# Creates a new access control area with default roles (View, Add, Manage, Change, Delete). Source: https://ekso.dev/api-reference/adminaccesscontrol/creates-a-new-access-control-area-with-default-roles-view-add-manage-change-delete /api-reference/openapi.json post /api/access-control # Deletes an access control rule by its identifier. Source: https://ekso.dev/api-reference/adminaccesscontrol/deletes-an-access-control-rule-by-its-identifier /api-reference/openapi.json delete /api/access-control/{id} # Lists all access control rules for the current tenant. Source: https://ekso.dev/api-reference/adminaccesscontrol/lists-all-access-control-rules-for-the-current-tenant /api-reference/openapi.json get /api/access-control # Retrieves a single access control rule by its identifier. Source: https://ekso.dev/api-reference/adminaccesscontrol/retrieves-a-single-access-control-rule-by-its-identifier /api-reference/openapi.json get /api/access-control/{id} # Updates an existing access control rule. Source: https://ekso.dev/api-reference/adminaccesscontrol/updates-an-existing-access-control-rule /api-reference/openapi.json put /api/access-control # Adds a domain or subject to the mail block list. Source: https://ekso.dev/api-reference/adminmailgeneral/adds-a-domain-or-subject-to-the-mail-block-list /api-reference/openapi.json put /api/mail/general/block # Replaces the general mail settings with the provided configuration. Source: https://ekso.dev/api-reference/adminmailgeneral/replaces-the-general-mail-settings-with-the-provided-configuration /api-reference/openapi.json put /api/mail/general # Retrieves the general mail settings including signature and stop lists. Source: https://ekso.dev/api-reference/adminmailgeneral/retrieves-the-general-mail-settings-including-signature-and-stop-lists /api-reference/openapi.json get /api/mail/general # Creates a new mailbox configuration, encrypting credentials before storage. Source: https://ekso.dev/api-reference/adminmailmailbox/creates-a-new-mailbox-configuration-encrypting-credentials-before-storage /api-reference/openapi.json post /api/mail/mailbox # Deletes a mailbox configuration by identifier. Source: https://ekso.dev/api-reference/adminmailmailbox/deletes-a-mailbox-configuration-by-identifier /api-reference/openapi.json delete /api/mail/mailbox/{id} # Lists all mailbox configurations with decrypted credentials. Source: https://ekso.dev/api-reference/adminmailmailbox/lists-all-mailbox-configurations-with-decrypted-credentials /api-reference/openapi.json get /api/mail/mailbox # Retrieves a single mailbox configuration by identifier with decrypted credentials. Source: https://ekso.dev/api-reference/adminmailmailbox/retrieves-a-single-mailbox-configuration-by-identifier-with-decrypted-credentials /api-reference/openapi.json get /api/mail/mailbox/{id} # Tests connectivity to the mail server for the specified mailbox. Source: https://ekso.dev/api-reference/adminmailmailbox/tests-connectivity-to-the-mail-server-for-the-specified-mailbox /api-reference/openapi.json get /api/mail/mailbox/{id}/test # Updates an existing mailbox configuration, encrypting credentials before storage. Source: https://ekso.dev/api-reference/adminmailmailbox/updates-an-existing-mailbox-configuration-encrypting-credentials-before-storage /api-reference/openapi.json put /api/mail/mailbox/{id} # Creates a new ticketing configuration for processing incoming email into tickets. Source: https://ekso.dev/api-reference/adminmailticketing/creates-a-new-ticketing-configuration-for-processing-incoming-email-into-tickets /api-reference/openapi.json post /api/mail/ticketing # Deletes a ticketing configuration by identifier. Source: https://ekso.dev/api-reference/adminmailticketing/deletes-a-ticketing-configuration-by-identifier /api-reference/openapi.json delete /api/mail/ticketing/{id} # Lists all ticketing configurations for the current tenant. Source: https://ekso.dev/api-reference/adminmailticketing/lists-all-ticketing-configurations-for-the-current-tenant /api-reference/openapi.json get /api/mail/ticketing # Retrieves a single ticketing configuration by identifier. Source: https://ekso.dev/api-reference/adminmailticketing/retrieves-a-single-ticketing-configuration-by-identifier /api-reference/openapi.json get /api/mail/ticketing/{id} # Runs a test ticketing cycle for the specified configuration to verify end-to-end processing. Source: https://ekso.dev/api-reference/adminmailticketing/runs-a-test-ticketing-cycle-for-the-specified-configuration-to-verify-end-to-end-processing /api-reference/openapi.json get /api/mail/ticketing/{id}/test # Updates an existing ticketing configuration. Source: https://ekso.dev/api-reference/adminmailticketing/updates-an-existing-ticketing-configuration /api-reference/openapi.json put /api/mail/ticketing/{id} # Creates a new user (super admin only). Source: https://ekso.dev/api-reference/adminuser/creates-a-new-user-super-admin-only /api-reference/openapi.json post /api/user # Deletes a user by their identifier (super admin only). Source: https://ekso.dev/api-reference/adminuser/deletes-a-user-by-their-identifier-super-admin-only /api-reference/openapi.json delete /api/user/{id} # Lists all users for the current tenant. Source: https://ekso.dev/api-reference/adminuser/lists-all-users-for-the-current-tenant /api-reference/openapi.json get /api/user # Retrieves a single user by their identifier (non-admins can only retrieve their own record). Source: https://ekso.dev/api-reference/adminuser/retrieves-a-single-user-by-their-identifier-non-admins-can-only-retrieve-their-own-record /api-reference/openapi.json get /api/user/{id} # Updates an existing user (non-admins can only update their own record). Source: https://ekso.dev/api-reference/adminuser/updates-an-existing-user-non-admins-can-only-update-their-own-record /api-reference/openapi.json put /api/user # Creates a new custom user group (super admin only). Source: https://ekso.dev/api-reference/adminusergroup/creates-a-new-custom-user-group-super-admin-only /api-reference/openapi.json post /api/security/user-group # Deletes a custom user group by its identifier (super admin only, system groups cannot be deleted). Source: https://ekso.dev/api-reference/adminusergroup/deletes-a-custom-user-group-by-its-identifier-super-admin-only-system-groups-cannot-be-deleted /api-reference/openapi.json delete /api/security/user-group/{id} # Lists all user groups for the current tenant, ordered by name. Source: https://ekso.dev/api-reference/adminusergroup/lists-all-user-groups-for-the-current-tenant-ordered-by-name /api-reference/openapi.json get /api/security/user-group # Retrieves a single user group by its identifier. Source: https://ekso.dev/api-reference/adminusergroup/retrieves-a-single-user-group-by-its-identifier /api-reference/openapi.json get /api/security/user-group/{id} # Updates an existing user group (super admin only, system groups cannot be deleted via update). Source: https://ekso.dev/api-reference/adminusergroup/updates-an-existing-user-group-super-admin-only-system-groups-cannot-be-deleted-via-update /api-reference/openapi.json put /api/security/user-group # Creates a new annotation on the specified owner entity. Source: https://ekso.dev/api-reference/annotation/creates-a-new-annotation-on-the-specified-owner-entity /api-reference/openapi.json post /api/annotation # Deletes an annotation by its identifier. Source: https://ekso.dev/api-reference/annotation/deletes-an-annotation-by-its-identifier /api-reference/openapi.json delete /api/annotation/{id} # Lists all annotations belonging to a specific owner, ordered by creation date ascending. Source: https://ekso.dev/api-reference/annotation/lists-all-annotations-belonging-to-a-specific-owner-ordered-by-creation-date-ascending /api-reference/openapi.json get /api/annotation/owner/{ownerId} # Retrieves a single annotation by its identifier. Source: https://ekso.dev/api-reference/annotation/retrieves-a-single-annotation-by-its-identifier /api-reference/openapi.json get /api/annotation/{id} # Returns the list of supported annotation content types. Source: https://ekso.dev/api-reference/annotation/returns-the-list-of-supported-annotation-content-types /api-reference/openapi.json get /api/annotation/supported # Updates an existing annotation by its identifier. Source: https://ekso.dev/api-reference/annotation/updates-an-existing-annotation-by-its-identifier /api-reference/openapi.json put /api/annotation/{id} # Lists all API keys for the current tenant (most recently created first). Hashes are stripped from the returned records; only the display prefix and metadata are visible. Source: https://ekso.dev/api-reference/apikey/lists-all-api-keys-for-the-current-tenant-most-recently-created-first-hashes-arestripped-from-the-returned-records;-only-the-display-prefix-and-metadata-are-visible /api-reference/openapi.json get /api/admin/api-key # Mints a new API key. The full plaintext key is returned in the response EXACTLY ONCE. The caller must capture and store it immediately -- there is no way to retrieve it later. Source: https://ekso.dev/api-reference/apikey/mints-a-new-api-key-the-full-plaintext-key-is-returned-in-the-response-exactly-oncethe-caller-must-capture-and-store-it-immediately--there-is-no-way-to-retrieve-it-later /api-reference/openapi.json post /api/admin/api-key # Revokes an API key. Soft-delete -- the row is preserved for audit; the key stops working immediately. Idempotent: revoking an already-revoked key is a no-op. Source: https://ekso.dev/api-reference/apikey/revokes-an-api-key-soft-delete--the-row-is-preserved-for-audit;-the-key-stopsworking-immediately-idempotent:-revoking-an-already-revoked-key-is-a-no-op /api-reference/openapi.json delete /api/admin/api-key/{id} # Creates a new application setting. Source: https://ekso.dev/api-reference/appsetting/creates-a-new-application-setting /api-reference/openapi.json post /api/admin/setting # Deletes an application setting by identifier. Source: https://ekso.dev/api-reference/appsetting/deletes-an-application-setting-by-identifier /api-reference/openapi.json delete /api/admin/setting/{id} # Lists all application settings for the current tenant. Source: https://ekso.dev/api-reference/appsetting/lists-all-application-settings-for-the-current-tenant /api-reference/openapi.json get /api/admin/setting # Retrieves a single application setting by identifier. Source: https://ekso.dev/api-reference/appsetting/retrieves-a-single-application-setting-by-identifier /api-reference/openapi.json get /api/admin/setting/{id} # Updates an existing application setting. Source: https://ekso.dev/api-reference/appsetting/updates-an-existing-application-setting /api-reference/openapi.json put /api/admin/setting # Creates a new area (super admin only). Source: https://ekso.dev/api-reference/area/creates-a-new-area-super-admin-only /api-reference/openapi.json post /api/area # Deletes an area by its identifier (super admin only). Source: https://ekso.dev/api-reference/area/deletes-an-area-by-its-identifier-super-admin-only /api-reference/openapi.json delete /api/area/{id} # Lists all areas for the current tenant. Source: https://ekso.dev/api-reference/area/lists-all-areas-for-the-current-tenant /api-reference/openapi.json get /api/area # Retrieves a single area by its identifier. Source: https://ekso.dev/api-reference/area/retrieves-a-single-area-by-its-identifier /api-reference/openapi.json get /api/area/{id} # Updates an existing area. Source: https://ekso.dev/api-reference/area/updates-an-existing-area /api-reference/openapi.json put /api/area # Creates a new board. Source: https://ekso.dev/api-reference/board/creates-a-new-board /api-reference/openapi.json post /api/board # Deletes a board by ID. Source: https://ekso.dev/api-reference/board/deletes-a-board-by-id /api-reference/openapi.json delete /api/board/{id} # Lists all boards visible to the current user. Source: https://ekso.dev/api-reference/board/lists-all-boards-visible-to-the-current-user /api-reference/openapi.json get /api/board # Retrieves a single board by ID. Source: https://ekso.dev/api-reference/board/retrieves-a-single-board-by-id /api-reference/openapi.json get /api/board/{id} # Returns a portfolio-level summary across all boards: aggregate metrics and per-board health, financials, item counts, cycle counts, and status distribution. Source: https://ekso.dev/api-reference/board/returns-a-portfolio-level-summary-across-all-boards:-aggregate-metrics-and-per-boardhealth-financials-item-counts-cycle-counts-and-status-distribution /api-reference/openapi.json get /api/board/portfolio # Returns item count statistics grouped by board. Source: https://ekso.dev/api-reference/board/returns-item-count-statistics-grouped-by-board /api-reference/openapi.json get /api/board/stats # Updates an existing board. Source: https://ekso.dev/api-reference/board/updates-an-existing-board /api-reference/openapi.json put /api/board/{id} # Creates a new budget reason code. Source: https://ekso.dev/api-reference/budgetreason/creates-a-new-budget-reason-code /api-reference/openapi.json post /api/finance/reason-code # Deletes a budget reason code by its identifier. Source: https://ekso.dev/api-reference/budgetreason/deletes-a-budget-reason-code-by-its-identifier /api-reference/openapi.json delete /api/finance/reason-code/{id} # Lists all budget reason codes for the current tenant. Source: https://ekso.dev/api-reference/budgetreason/lists-all-budget-reason-codes-for-the-current-tenant /api-reference/openapi.json get /api/finance/reason-code # Retrieves a single budget reason code by its identifier. Source: https://ekso.dev/api-reference/budgetreason/retrieves-a-single-budget-reason-code-by-its-identifier /api-reference/openapi.json get /api/finance/reason-code/{id} # Updates an existing budget reason code. Source: https://ekso.dev/api-reference/budgetreason/updates-an-existing-budget-reason-code /api-reference/openapi.json put /api/finance/reason-code # Deletes an AI chat conversation by ID. Source: https://ekso.dev/api-reference/chat/deletes-an-ai-chat-conversation-by-id /api-reference/openapi.json delete /api/ai/conversation/{id} # Lists all AI chat conversations belonging to the current user. Source: https://ekso.dev/api-reference/chat/lists-all-ai-chat-conversations-belonging-to-the-current-user /api-reference/openapi.json get /api/ai/conversation # Performs a semantic vector search across indexed content using the provided phrase. Source: https://ekso.dev/api-reference/chat/performs-a-semantic-vector-search-across-indexed-content-using-the-provided-phrase /api-reference/openapi.json post /api/ai/search # Sends a question to the AI assistant and returns the response, persisting the conversation history. Source: https://ekso.dev/api-reference/chat/sends-a-question-to-the-ai-assistant-and-returns-the-response-persisting-the-conversation-history /api-reference/openapi.json post /api/ai/ask # Creates a new business clock configuration. Source: https://ekso.dev/api-reference/clock/creates-a-new-business-clock-configuration /api-reference/openapi.json post /api/clock # Deletes a business clock configuration by identifier. Source: https://ekso.dev/api-reference/clock/deletes-a-business-clock-configuration-by-identifier /api-reference/openapi.json delete /api/clock/{id} # Lists all business clock configurations for the current tenant. Source: https://ekso.dev/api-reference/clock/lists-all-business-clock-configurations-for-the-current-tenant /api-reference/openapi.json get /api/clock # Retrieves a single business clock configuration by identifier. Source: https://ekso.dev/api-reference/clock/retrieves-a-single-business-clock-configuration-by-identifier /api-reference/openapi.json get /api/clock/{id} # Updates an existing business clock configuration. Source: https://ekso.dev/api-reference/clock/updates-an-existing-business-clock-configuration /api-reference/openapi.json put /api/clock/{id} # Retrieves the forms-based authentication configuration for the current tenant. Source: https://ekso.dev/api-reference/config/retrieves-the-forms-based-authentication-configuration-for-the-current-tenant /api-reference/openapi.json get /api/config/forms-auth # Retrieves the Microsoft Entra ID authentication configuration for the current tenant. Source: https://ekso.dev/api-reference/config/retrieves-the-microsoft-entra-id-authentication-configuration-for-the-current-tenant /api-reference/openapi.json get /api/config/entra-auth # Updates the forms-based authentication configuration for the current tenant. Source: https://ekso.dev/api-reference/config/updates-the-forms-based-authentication-configuration-for-the-current-tenant /api-reference/openapi.json put /api/config/forms-auth # Updates the Microsoft Entra ID authentication configuration for the current tenant. Source: https://ekso.dev/api-reference/config/updates-the-microsoft-entra-id-authentication-configuration-for-the-current-tenant /api-reference/openapi.json put /api/config/entra-auth # Creates a new constraint. Source: https://ekso.dev/api-reference/constraint/creates-a-new-constraint /api-reference/openapi.json post /api/constraint # Deletes a constraint by its identifier. Source: https://ekso.dev/api-reference/constraint/deletes-a-constraint-by-its-identifier /api-reference/openapi.json delete /api/constraint/{id} # Lists all constraints for the current tenant. Source: https://ekso.dev/api-reference/constraint/lists-all-constraints-for-the-current-tenant /api-reference/openapi.json get /api/constraint # Lists all constraints that belong to a specific container. Source: https://ekso.dev/api-reference/constraint/lists-all-constraints-that-belong-to-a-specific-container /api-reference/openapi.json get /api/constraint/container/{containerId} # Retrieves a single constraint by its identifier. Source: https://ekso.dev/api-reference/constraint/retrieves-a-single-constraint-by-its-identifier /api-reference/openapi.json get /api/constraint/{id} # Updates an existing constraint. Source: https://ekso.dev/api-reference/constraint/updates-an-existing-constraint /api-reference/openapi.json put /api/constraint/{id} # Creates a new container. Source: https://ekso.dev/api-reference/container/creates-a-new-container /api-reference/openapi.json post /api/container # Creates a new filter within a container. Source: https://ekso.dev/api-reference/container/creates-a-new-filter-within-a-container /api-reference/openapi.json post /api/container/{containerId}/filter # Deletes a container by its identifier. Source: https://ekso.dev/api-reference/container/deletes-a-container-by-its-identifier /api-reference/openapi.json delete /api/container/{id} # Deletes a filter from a container. Source: https://ekso.dev/api-reference/container/deletes-a-filter-from-a-container /api-reference/openapi.json delete /api/container/{containerId}/filter/{filterId} # Lists all containers the current user has view permission for. Source: https://ekso.dev/api-reference/container/lists-all-containers-the-current-user-has-view-permission-for /api-reference/openapi.json get /api/container # Lists all filters for a container. Source: https://ekso.dev/api-reference/container/lists-all-filters-for-a-container /api-reference/openapi.json get /api/container/{containerId}/filter # Re-sequences item numbers within a container. Source: https://ekso.dev/api-reference/container/re-sequences-item-numbers-within-a-container /api-reference/openapi.json post /api/container/{id}/sequence # Retrieves a single container by its identifier. Source: https://ekso.dev/api-reference/container/retrieves-a-single-container-by-its-identifier /api-reference/openapi.json get /api/container/{id} # Retrieves a single filter within a container. Source: https://ekso.dev/api-reference/container/retrieves-a-single-filter-within-a-container /api-reference/openapi.json get /api/container/{containerId}/filter/{filterId} # Updates an existing container. Source: https://ekso.dev/api-reference/container/updates-an-existing-container /api-reference/openapi.json put /api/container # Updates an existing filter within a container. Source: https://ekso.dev/api-reference/container/updates-an-existing-filter-within-a-container /api-reference/openapi.json put /api/container/{containerId}/filter/{filterId} # Creates a new content template. Source: https://ekso.dev/api-reference/contenttemplate/creates-a-new-content-template /api-reference/openapi.json post /api/template/content # Deletes a content template by its identifier. Source: https://ekso.dev/api-reference/contenttemplate/deletes-a-content-template-by-its-identifier /api-reference/openapi.json delete /api/template/content/{id} # Lists all content templates for the current tenant. Source: https://ekso.dev/api-reference/contenttemplate/lists-all-content-templates-for-the-current-tenant /api-reference/openapi.json get /api/template/content # Retrieves a single content template by its identifier. Source: https://ekso.dev/api-reference/contenttemplate/retrieves-a-single-content-template-by-its-identifier /api-reference/openapi.json get /api/template/content/{id} # Updates an existing content template. Source: https://ekso.dev/api-reference/contenttemplate/updates-an-existing-content-template /api-reference/openapi.json put /api/template/content # Creates a new cost center. Source: https://ekso.dev/api-reference/costcenter/creates-a-new-cost-center /api-reference/openapi.json post /api/finance/cost # Deletes a cost center by its identifier. Source: https://ekso.dev/api-reference/costcenter/deletes-a-cost-center-by-its-identifier /api-reference/openapi.json delete /api/finance/cost/{id} # Lists all cost centers for the current tenant. Source: https://ekso.dev/api-reference/costcenter/lists-all-cost-centers-for-the-current-tenant /api-reference/openapi.json get /api/finance/cost # Retrieves a single cost center by its identifier. Source: https://ekso.dev/api-reference/costcenter/retrieves-a-single-cost-center-by-its-identifier /api-reference/openapi.json get /api/finance/cost/{id} # Updates an existing cost center. Source: https://ekso.dev/api-reference/costcenter/updates-an-existing-cost-center /api-reference/openapi.json put /api/finance/cost # Creates a new CRM record. Source: https://ekso.dev/api-reference/crm/creates-a-new-crm-record /api-reference/openapi.json post /api/finance/crm # Deletes a CRM record by its identifier. Source: https://ekso.dev/api-reference/crm/deletes-a-crm-record-by-its-identifier /api-reference/openapi.json delete /api/finance/crm/{id} # Lists all CRM records for the current tenant. Source: https://ekso.dev/api-reference/crm/lists-all-crm-records-for-the-current-tenant /api-reference/openapi.json get /api/finance/crm # Retrieves a single CRM record by its identifier. Source: https://ekso.dev/api-reference/crm/retrieves-a-single-crm-record-by-its-identifier /api-reference/openapi.json get /api/finance/crm/{id} # Updates an existing CRM record. Source: https://ekso.dev/api-reference/crm/updates-an-existing-crm-record /api-reference/openapi.json put /api/finance/crm # Creates a new cycle on the specified board. Source: https://ekso.dev/api-reference/cycle/creates-a-new-cycle-on-the-specified-board /api-reference/openapi.json post /api/board/{boardId}/cycle # Deletes a cycle by ID, only if it belongs to the specified board. Source: https://ekso.dev/api-reference/cycle/deletes-a-cycle-by-id-only-if-it-belongs-to-the-specified-board /api-reference/openapi.json delete /api/board/{boardId}/cycle/{cycleId} # Lists all cycles across all boards. Source: https://ekso.dev/api-reference/cycle/lists-all-cycles-across-all-boards /api-reference/openapi.json get /api/board/cycle # Lists all cycles for a specific board. Source: https://ekso.dev/api-reference/cycle/lists-all-cycles-for-a-specific-board /api-reference/openapi.json get /api/board/{boardId}/cycle # Lists all items assigned to a specific cycle within a board. Source: https://ekso.dev/api-reference/cycle/lists-all-items-assigned-to-a-specific-cycle-within-a-board /api-reference/openapi.json get /api/board/{boardId}/cycle/{cycleId}/item # Retrieves a single cycle by ID within a board. Source: https://ekso.dev/api-reference/cycle/retrieves-a-single-cycle-by-id-within-a-board /api-reference/openapi.json get /api/board/{boardId}/cycle/{cycleId} # Returns item count statistics grouped by cycle. Source: https://ekso.dev/api-reference/cycle/returns-item-count-statistics-grouped-by-cycle /api-reference/openapi.json get /api/cycle/stats # Updates an existing cycle, and automatically approves all item time entries if the cycle is being closed. Source: https://ekso.dev/api-reference/cycle/updates-an-existing-cycle-and-automatically-approves-all-item-time-entries-if-the-cycle-is-being-closed /api-reference/openapi.json put /api/board/{boardId}/cycle/{cycleId} # Creates a new document and initializes its empty content record. Source: https://ekso.dev/api-reference/docs/creates-a-new-document-and-initializes-its-empty-content-record /api-reference/openapi.json post /api/docs/doc # Creates a new document folder. Source: https://ekso.dev/api-reference/docs/creates-a-new-document-folder /api-reference/openapi.json post /api/docs/folder # Deletes a document by its identifier. Source: https://ekso.dev/api-reference/docs/deletes-a-document-by-its-identifier /api-reference/openapi.json delete /api/docs/doc/{docId} # Deletes a document folder by its identifier. Source: https://ekso.dev/api-reference/docs/deletes-a-document-folder-by-its-identifier /api-reference/openapi.json delete /api/docs/folder/{folderId} # Lists all active documents within a specific folder, filtered by user permissions. Source: https://ekso.dev/api-reference/docs/lists-all-active-documents-within-a-specific-folder-filtered-by-user-permissions /api-reference/openapi.json get /api/docs/folder/{folderId}/list # Lists all document folders visible to the current user. Source: https://ekso.dev/api-reference/docs/lists-all-document-folders-visible-to-the-current-user /api-reference/openapi.json get /api/docs/folder # Retrieves a single document by its identifier. Source: https://ekso.dev/api-reference/docs/retrieves-a-single-document-by-its-identifier /api-reference/openapi.json get /api/docs/doc/{docId} # Retrieves a single document folder by its identifier. Source: https://ekso.dev/api-reference/docs/retrieves-a-single-document-folder-by-its-identifier /api-reference/openapi.json get /api/docs/folder/{folderId} # Retrieves the content body of a document. Source: https://ekso.dev/api-reference/docs/retrieves-the-content-body-of-a-document /api-reference/openapi.json get /api/docs/doc/{docId}/content # Updates an existing document by its identifier. Source: https://ekso.dev/api-reference/docs/updates-an-existing-document-by-its-identifier /api-reference/openapi.json put /api/docs/doc/{docId} # Updates an existing document folder by its identifier. Source: https://ekso.dev/api-reference/docs/updates-an-existing-document-folder-by-its-identifier /api-reference/openapi.json put /api/docs/folder/{folderId} # Updates the content body of a document. Source: https://ekso.dev/api-reference/docs/updates-the-content-body-of-a-document /api-reference/openapi.json put /api/docs/doc/{docId}/content # Returns the public Entra ID configuration (tenant ID, app ID) needed by the front-end MSAL login flow. Source: https://ekso.dev/api-reference/entrameta/returns-the-public-entra-id-configuration-tenant-id-app-id-needed-by-the-front-end-msal-login-flow /api-reference/openapi.json get /api/auth/entra-endpoint # Creates a new custom date field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-date-field /api-reference/openapi.json post /api/field/date # Creates a new custom decimal field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-decimal-field /api-reference/openapi.json post /api/field/decimal # Creates a new custom integer field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-integer-field /api-reference/openapi.json post /api/field/integer # Creates a new custom list (dropdown) field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-list-dropdown-field /api-reference/openapi.json post /api/field/list # Creates a new custom percent field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-percent-field /api-reference/openapi.json post /api/field/percent # Creates a new custom picker field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-picker-field /api-reference/openapi.json post /api/field/picker # Creates a new custom text field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-text-field /api-reference/openapi.json post /api/field/text # Creates a new custom time field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-time-field /api-reference/openapi.json post /api/field/time # Creates a new custom toggle (boolean) field. Source: https://ekso.dev/api-reference/field/creates-a-new-custom-toggle-boolean-field /api-reference/openapi.json post /api/field/toggle # Returns the complete collection of core and custom fields for the current tenant. Source: https://ekso.dev/api-reference/field/returns-the-complete-collection-of-core-and-custom-fields-for-the-current-tenant /api-reference/openapi.json get /api/field # Updates an existing custom date field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-date-field /api-reference/openapi.json put /api/field/date # Updates an existing custom decimal field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-decimal-field /api-reference/openapi.json put /api/field/decimal # Updates an existing custom integer field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-integer-field /api-reference/openapi.json put /api/field/integer # Updates an existing custom list (dropdown) field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-list-dropdown-field /api-reference/openapi.json put /api/field/list # Updates an existing custom percent field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-percent-field /api-reference/openapi.json put /api/field/percent # Updates an existing custom picker field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-picker-field /api-reference/openapi.json put /api/field/picker # Updates an existing custom text field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-text-field /api-reference/openapi.json put /api/field/text # Updates an existing custom time field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-time-field /api-reference/openapi.json put /api/field/time # Updates an existing custom toggle (boolean) field. Source: https://ekso.dev/api-reference/field/updates-an-existing-custom-toggle-boolean-field /api-reference/openapi.json put /api/field/toggle # Updates the list values (and default-value pointer) of a list field. Source: https://ekso.dev/api-reference/field/updates-the-list-values-and-default-value-pointer-of-a-list-field /api-reference/openapi.json put /api/field/list/data Designed as the CLI/SDK escape hatch for system fields like Priority — the broader `PUT /api/field/list` rejects `IsCore=true` mutations from CLI/SDK callers, but admins still need to add "Critical" to Priority via script. Allowed for any caller. All other field properties (name, description, multi-select, IsCore, etc.) are preserved from the existing record. # Deletes a file and removes its blob from storage. Source: https://ekso.dev/api-reference/file/deletes-a-file-and-removes-its-blob-from-storage /api-reference/openapi.json delete /api/file/{id} # Downloads a file's binary content from storage. Source: https://ekso.dev/api-reference/file/downloads-a-files-binary-content-from-storage /api-reference/openapi.json get /api/file/download/{fileId} # Lists all files attached to a specific owner, filtered by the current user's view permissions. Source: https://ekso.dev/api-reference/file/lists-all-files-attached-to-a-specific-owner-filtered-by-the-current-users-view-permissions /api-reference/openapi.json get /api/file/{ownerType}/{ownerId} # Retrieves a single file's metadata by its identifier. Useful when you have the file id (e.g. from an upload response) and want to verify existence or fetch metadata without listing the owner's full file list. Source: https://ekso.dev/api-reference/file/retrieves-a-single-files-metadata-by-its-identifier-useful-when-you-have-thefile-id-eg-from-an-upload-response-and-want-to-verify-existence-or-fetchmetadata-without-listing-the-owners-full-file-list /api-reference/openapi.json get /api/file/{id} # Uploads a single file and attaches it to the specified owner entity. Returns the created DataFile record so callers (CLI / SDK / migration scripts) can capture the new file id without a follow-up list-and-filter call. Source: https://ekso.dev/api-reference/file/uploads-a-single-file-and-attaches-it-to-the-specified-owner-entity-returns-thecreated-datafile-record-so-callers-cli-sdk-migration-scriptscan-capture-the-new-file-id-without-a-follow-up-list-and-filter-call /api-reference/openapi.json post /api/file/upload/{ownerType}/{ownerId} # Retrieves the webhook configuration for the current tenant. Source: https://ekso.dev/api-reference/hook/retrieves-the-webhook-configuration-for-the-current-tenant /api-reference/openapi.json get /api/admin/hook # Updates the webhook configuration for the current tenant. Source: https://ekso.dev/api-reference/hook/updates-the-webhook-configuration-for-the-current-tenant /api-reference/openapi.json put /api/admin/hook # Introduction Source: https://ekso.dev/api-reference/introduction Ekso REST API reference — authentication, error handling, versioning, and rate limits. ## Base URL All API requests are made to your install's URL — whatever public hostname you set during the [first-run wizard](/guide/install/first-run): ```text theme={null} https://ekso.acme.com/api/ ``` Self-host has no concept of a tenant subdomain — every install is its own single addressable identity. ## Authentication The Ekso API uses **Bearer token** authentication. Include your JWT token in the `Authorization` header of every request: ```text theme={null} Authorization: Bearer ``` Tokens are obtained via the [OAuth 2.0 authorization code flow](/api-reference/introduction#oauth-20) with PKCE. ### OAuth 2.0 Ekso implements OAuth 2.0 with the following endpoints: | Endpoint | URL | | ------------------- | ----------------------------------------- | | Discovery | `/.well-known/oauth-authorization-server` | | Authorization | `/authorize` | | Token | `/token` | | Client registration | `/register` | **Supported grant types:** `authorization_code`, `refresh_token` **Code challenge method:** S256 (PKCE required) ## Error handling All error responses share a consistent shape: ```json theme={null} { "kind": "Validation", "message": "Human-readable error description", "fields": [ { "field": "email", "code": "required" } ] } ``` | HTTP Status | Kind | When | | ----------- | ------------ | ------------------------------------------------------------------------ | | 400 | `Validation` | Field-level validation errors — check the `fields` array | | 403 | `Permission` | Insufficient permissions for this action | | 422 | `Logic` | Business rule violation (e.g., item not found, invalid state transition) | | 500 | `Exception` | Internal server error | The `fields` array is only populated for `Validation` errors. ## Versioning The API supports versioning via multiple methods: | Method | Example | | ------------ | --------------------------------------------- | | Query string | `?api-version=1.0` | | URL segment | `/api/v1.0/...` | | Header | `x-api-version: 1.0` | | Media type | `Accept: application/json; x-api-version=1.0` | If no version is specified, the latest version is used. ## Rate limits API endpoints are rate-limited to **50 requests per second** per IP address. When the limit is exceeded, the API returns HTTP `429 Too Many Requests`. ## Response format All responses use **JSON** (`application/json`). Successful responses return HTTP 200 with the data payload directly in the response body. # Adds a parent-child dependency between two items. Source: https://ekso.dev/api-reference/item/adds-a-parent-child-dependency-between-two-items /api-reference/openapi.json post /api/item/dependency/add/{parentId}/{childId} # Creates a new item within a container. Source: https://ekso.dev/api-reference/item/creates-a-new-item-within-a-container /api-reference/openapi.json post /api/item # Deletes an item by its identifier. Source: https://ekso.dev/api-reference/item/deletes-an-item-by-its-identifier /api-reference/openapi.json delete /api/item/{id} # Duplicates an existing item within the same container. Source: https://ekso.dev/api-reference/item/duplicates-an-existing-item-within-the-same-container /api-reference/openapi.json post /api/item/copy # Executes a filter query against items and returns paginated results. Source: https://ekso.dev/api-reference/item/executes-a-filter-query-against-items-and-returns-paginated-results /api-reference/openapi.json post /api/item/filter # Finds items by container ID and/or sequence number. Source: https://ekso.dev/api-reference/item/finds-items-by-container-id-andor-sequence-number /api-reference/openapi.json post /api/item/find # Lists items assigned to a specific resource (user or job role). Source: https://ekso.dev/api-reference/item/lists-items-assigned-to-a-specific-resource-user-or-job-role /api-reference/openapi.json post /api/item/resource # Lists items in a container with optional filtering and pagination. Source: https://ekso.dev/api-reference/item/lists-items-in-a-container-with-optional-filtering-and-pagination /api-reference/openapi.json post /api/item/list # Removes an item from its dependency tree, making it a standalone item that retains its children. Source: https://ekso.dev/api-reference/item/removes-an-item-from-its-dependency-tree-making-it-a-standalone-item-that-retains-its-children /api-reference/openapi.json post /api/item/dependency/remove/{id} # Retrieves a single item by its identifier, returning an empty item if the user lacks permission. Source: https://ekso.dev/api-reference/item/retrieves-a-single-item-by-its-identifier-returning-an-empty-item-if-the-user-lacks-permission /api-reference/openapi.json post /api/item/{id} # Retrieves the full dependency tree for an item, filtered to items the user can see. Source: https://ekso.dev/api-reference/item/retrieves-the-full-dependency-tree-for-an-item-filtered-to-items-the-user-can-see /api-reference/openapi.json get /api/item/{id}/dependency # Searches for items matching a keyword across one or more containers, filtered by user permissions. Source: https://ekso.dev/api-reference/item/searches-for-items-matching-a-keyword-across-one-or-more-containers-filtered-by-user-permissions /api-reference/openapi.json post /api/item/match # Sends an outbound ticket reply as an annotation on an item. Source: https://ekso.dev/api-reference/item/sends-an-outbound-ticket-reply-as-an-annotation-on-an-item /api-reference/openapi.json post /api/item/{itemId}/reply # Updates an existing item. Source: https://ekso.dev/api-reference/item/updates-an-existing-item /api-reference/openapi.json put /api/item/{id} # Creates a new job role. Source: https://ekso.dev/api-reference/jobrole/creates-a-new-job-role /api-reference/openapi.json post /api/finance/role # Deletes a job role by its identifier. Source: https://ekso.dev/api-reference/jobrole/deletes-a-job-role-by-its-identifier /api-reference/openapi.json delete /api/finance/role/{id} # Lists all job roles for the current tenant. Source: https://ekso.dev/api-reference/jobrole/lists-all-job-roles-for-the-current-tenant /api-reference/openapi.json get /api/finance/role # Retrieves a single job role by its identifier. Source: https://ekso.dev/api-reference/jobrole/retrieves-a-single-job-role-by-its-identifier /api-reference/openapi.json get /api/finance/role/{id} # Updates an existing job role. Source: https://ekso.dev/api-reference/jobrole/updates-an-existing-job-role /api-reference/openapi.json put /api/finance/role # Creates a new label (super admin only). Source: https://ekso.dev/api-reference/label/creates-a-new-label-super-admin-only /api-reference/openapi.json post /api/label # Deletes a label by its identifier (super admin only). Source: https://ekso.dev/api-reference/label/deletes-a-label-by-its-identifier-super-admin-only /api-reference/openapi.json delete /api/label/{id} # Lists all labels for the current tenant. Source: https://ekso.dev/api-reference/label/lists-all-labels-for-the-current-tenant /api-reference/openapi.json get /api/label # Retrieves a single label by its identifier. Source: https://ekso.dev/api-reference/label/retrieves-a-single-label-by-its-identifier /api-reference/openapi.json get /api/label/{id} # Updates an existing label (super admin only). Source: https://ekso.dev/api-reference/label/updates-an-existing-label-super-admin-only /api-reference/openapi.json put /api/label # Creates a new link between two entities. Source: https://ekso.dev/api-reference/link/creates-a-new-link-between-two-entities /api-reference/openapi.json post /api/link # Deletes a link by its identifier. Source: https://ekso.dev/api-reference/link/deletes-a-link-by-its-identifier /api-reference/openapi.json delete /api/link/{id} # Retrieves all resolved links for a given entity, including linked item details. Source: https://ekso.dev/api-reference/link/retrieves-all-resolved-links-for-a-given-entity-including-linked-item-details /api-reference/openapi.json get /api/link/{id} # Returns the list of supported ISO country codes. Source: https://ekso.dev/api-reference/meta/returns-the-list-of-supported-iso-country-codes /api-reference/openapi.json get /api/meta/country # Returns the list of supported ISO currency codes. Source: https://ekso.dev/api-reference/meta/returns-the-list-of-supported-iso-currency-codes /api-reference/openapi.json get /api/meta/currency # Returns the list of supported timezones with display names. Source: https://ekso.dev/api-reference/meta/returns-the-list-of-supported-timezones-with-display-names /api-reference/openapi.json get /api/meta/timezone # Creates a new non-billable code. Source: https://ekso.dev/api-reference/nonbillable/creates-a-new-non-billable-code /api-reference/openapi.json post /api/finance/non-billable # Deletes a non-billable code by its identifier. Source: https://ekso.dev/api-reference/nonbillable/deletes-a-non-billable-code-by-its-identifier /api-reference/openapi.json delete /api/finance/non-billable/{id} # Lists all non-billable codes for the current tenant. Source: https://ekso.dev/api-reference/nonbillable/lists-all-non-billable-codes-for-the-current-tenant /api-reference/openapi.json get /api/finance/non-billable # Retrieves a single non-billable code by its identifier. Source: https://ekso.dev/api-reference/nonbillable/retrieves-a-single-non-billable-code-by-its-identifier /api-reference/openapi.json get /api/finance/non-billable/{id} # Updates an existing non-billable code. Source: https://ekso.dev/api-reference/nonbillable/updates-an-existing-non-billable-code /api-reference/openapi.json put /api/finance/non-billable/{id} # Creates a new scheduled notification configuration. Source: https://ekso.dev/api-reference/notification/creates-a-new-scheduled-notification-configuration /api-reference/openapi.json post /api/notification # Deletes a notification configuration by identifier. Source: https://ekso.dev/api-reference/notification/deletes-a-notification-configuration-by-identifier /api-reference/openapi.json delete /api/notification/{id} # Lists all scheduled notification configurations for the current tenant. Source: https://ekso.dev/api-reference/notification/lists-all-scheduled-notification-configurations-for-the-current-tenant /api-reference/openapi.json get /api/notification # Retrieves a single notification configuration by identifier. Source: https://ekso.dev/api-reference/notification/retrieves-a-single-notification-configuration-by-identifier /api-reference/openapi.json get /api/notification/{id} # Updates an existing scheduled notification configuration. Source: https://ekso.dev/api-reference/notification/updates-an-existing-scheduled-notification-configuration /api-reference/openapi.json put /api/notification # AI planning assistant. Accepts a question and returns suggestions for item planning. Uses read-only MCP tools — cannot mutate data. Source: https://ekso.dev/api-reference/plan/ai-planning-assistant-accepts-a-question-and-returns-suggestions-for-item-planninguses-read-only-mcp-tools-—-cannot-mutate-data /api-reference/openapi.json post /api/plan/{boardId}/ai/ask # Batch-plans items into a cycle. All-or-nothing: if any item fails validation, none are saved. Source: https://ekso.dev/api-reference/plan/batch-plans-items-into-a-cycle-all-or-nothing:-if-any-item-fails-validation-none-are-saved /api-reference/openapi.json post /api/plan/{boardId}/batch # Calculates capacity for a board cycle: per-resource hours, utilization, and aggregates. Source: https://ekso.dev/api-reference/plan/calculates-capacity-for-a-board-cycle:-per-resource-hours-utilization-and-aggregates /api-reference/openapi.json get /api/plan/{boardId}/capacity # Moves an item from one cycle to another within the same board. Updates ordering in both source and target cycles. Source: https://ekso.dev/api-reference/plan/moves-an-item-from-one-cycle-to-another-within-the-same-boardupdates-ordering-in-both-source-and-target-cycles /api-reference/openapi.json post /api/plan/{boardId}/move # NOT USED: Auto-schedule frontend has been removed as part of the Board Revamp. This endpoint is retained for potential future use. Suggests optimal resource assignments for backlog items using greedy capacity balancing. Read-only — does not mutate data. Source: https://ekso.dev/api-reference/plan/not-used:-auto-schedule-frontend-has-been-removed-as-part-of-the-board-revampthis-endpoint-is-retained-for-potential-future-usesuggests-optimal-resource-assignments-for-backlog-items-using-greedy-capacity-balancingread-only-—-does-not-mutate-data /api-reference/openapi.json post /api/plan/{boardId}/auto-schedule # Removes an item from a board and cycle, returning it to the backlog. Source: https://ekso.dev/api-reference/plan/removes-an-item-from-a-board-and-cycle-returning-it-to-the-backlog /api-reference/openapi.json post /api/plan/{boardId}/unplan # Replaces the item ordering within a cycle. Source: https://ekso.dev/api-reference/plan/replaces-the-item-ordering-within-a-cycle /api-reference/openapi.json post /api/plan/{boardId}/reorder # Returns a resource-by-cycle utilization heatmap for a board. Source: https://ekso.dev/api-reference/plan/returns-a-resource-by-cycle-utilization-heatmap-for-a-board /api-reference/openapi.json get /api/plan/{boardId}/heatmap # Returns the count of unplanned items per board the user can view. Lightweight endpoint for badge display. Source: https://ekso.dev/api-reference/plan/returns-the-count-of-unplanned-items-per-board-the-user-can-viewlightweight-endpoint-for-badge-display /api-reference/openapi.json get /api/plan/stats/unplanned # Returns unplanned items across the board's whitelisted containers. Items without a board/cycle assignment that the user can see. Source: https://ekso.dev/api-reference/plan/returns-unplanned-items-across-the-boards-whitelisted-containersitems-without-a-boardcycle-assignment-that-the-user-can-see /api-reference/openapi.json get /api/plan/{boardId}/backlog # Creates a new workflow process configuration. Source: https://ekso.dev/api-reference/process/creates-a-new-workflow-process-configuration /api-reference/openapi.json post /api/process # Deletes a workflow process and removes it from all containers. Source: https://ekso.dev/api-reference/process/deletes-a-workflow-process-and-removes-it-from-all-containers /api-reference/openapi.json delete /api/process/{id} # Lists all workflow process configurations for the current tenant. Source: https://ekso.dev/api-reference/process/lists-all-workflow-process-configurations-for-the-current-tenant /api-reference/openapi.json get /api/process # Retrieves a single workflow process configuration by identifier. Source: https://ekso.dev/api-reference/process/retrieves-a-single-workflow-process-configuration-by-identifier /api-reference/openapi.json get /api/process/{id} # Returns item count statistics grouped by process type. Source: https://ekso.dev/api-reference/process/returns-item-count-statistics-grouped-by-process-type /api-reference/openapi.json get /api/process/stats # Updates an existing workflow process and synchronizes field removals across all containers. Source: https://ekso.dev/api-reference/process/updates-an-existing-workflow-process-and-synchronizes-field-removals-across-all-containers /api-reference/openapi.json put /api/process/{id} # Returns a per-user summary of logged time for the specified reporting period. Source: https://ekso.dev/api-reference/reporting/returns-a-per-user-summary-of-logged-time-for-the-specified-reporting-period /api-reference/openapi.json post /api/insight/report/user # Returns a weekly summary of logged time for the specified reporting period. Source: https://ekso.dev/api-reference/reporting/returns-a-weekly-summary-of-logged-time-for-the-specified-reporting-period /api-reference/openapi.json post /api/insight/report/time-week-summary # Returns billable item data for the specified reporting period. Source: https://ekso.dev/api-reference/reporting/returns-billable-item-data-for-the-specified-reporting-period /api-reference/openapi.json post /api/insight/report/item # Returns board cycle time risk analysis for the specified reporting period. Source: https://ekso.dev/api-reference/reporting/returns-board-cycle-time-risk-analysis-for-the-specified-reporting-period /api-reference/openapi.json post /api/insight/report/risk/board-cycle # Returns detailed profitability drilldown data for a specific item or grouping. Source: https://ekso.dev/api-reference/reporting/returns-detailed-profitability-drilldown-data-for-a-specific-item-or-grouping /api-reference/openapi.json post /api/insight/report/profitability/drilldown # Returns detailed timesheet entries for a specific user and reporting period. Source: https://ekso.dev/api-reference/reporting/returns-detailed-timesheet-entries-for-a-specific-user-and-reporting-period /api-reference/openapi.json post /api/insight/report/user-timesheet # Returns profitability data for the specified reporting period. Source: https://ekso.dev/api-reference/reporting/returns-profitability-data-for-the-specified-reporting-period /api-reference/openapi.json post /api/insight/report/profitability # Returns time code distribution data for the specified reporting period. Source: https://ekso.dev/api-reference/reporting/returns-time-code-distribution-data-for-the-specified-reporting-period /api-reference/openapi.json post /api/insight/report/time-code # Returns work estimate versus time logged risk data for the specified reporting period. Source: https://ekso.dev/api-reference/reporting/returns-work-estimate-versus-time-logged-risk-data-for-the-specified-reporting-period /api-reference/openapi.json post /api/insight/report/risk/estimate-logged # Creates a new automation rule configuration. Source: https://ekso.dev/api-reference/rule/creates-a-new-automation-rule-configuration /api-reference/openapi.json post /api/rule # Deletes an automation rule configuration by identifier. Source: https://ekso.dev/api-reference/rule/deletes-an-automation-rule-configuration-by-identifier /api-reference/openapi.json delete /api/rule/{id} # Lists all automation rule configurations for the current tenant. Source: https://ekso.dev/api-reference/rule/lists-all-automation-rule-configurations-for-the-current-tenant /api-reference/openapi.json get /api/rule # Retrieves a single automation rule configuration by identifier. Source: https://ekso.dev/api-reference/rule/retrieves-a-single-automation-rule-configuration-by-identifier /api-reference/openapi.json get /api/rule/{id} # Updates an existing automation rule configuration. Source: https://ekso.dev/api-reference/rule/updates-an-existing-automation-rule-configuration /api-reference/openapi.json put /api/rule # Returns the screen layout definition for adding or updating an item, including field definitions and workflow states. Source: https://ekso.dev/api-reference/screen/returns-the-screen-layout-definition-for-adding-or-updating-an-item-including-field-definitions-and-workflow-states /api-reference/openapi.json post /api/item/screen # Creates a new SKU. Source: https://ekso.dev/api-reference/sku/creates-a-new-sku /api-reference/openapi.json post /api/finance/sku # Deletes a SKU by its identifier. Source: https://ekso.dev/api-reference/sku/deletes-a-sku-by-its-identifier /api-reference/openapi.json delete /api/finance/sku/{id} # Lists all SKUs for the current tenant. Source: https://ekso.dev/api-reference/sku/lists-all-skus-for-the-current-tenant /api-reference/openapi.json get /api/finance/sku # Retrieves a single SKU by its identifier. Source: https://ekso.dev/api-reference/sku/retrieves-a-single-sku-by-its-identifier /api-reference/openapi.json get /api/finance/sku/{id} # Updates an existing SKU. Source: https://ekso.dev/api-reference/sku/updates-an-existing-sku /api-reference/openapi.json put /api/finance/sku # Retrieves the AI provider configuration for the current tenant. Hidden from the SDK/CLI because the response shape carries provider API keys; AI config is tenant infrastructure, not customer data ops or migration scope. Source: https://ekso.dev/api-reference/tenant/retrieves-the-ai-provider-configuration-for-the-current-tenant-hidden-from-thesdkcli-because-the-response-shape-carries-provider-api-keys;-ai-config-is-tenantinfrastructure-not-customer-data-ops-or-migration-scope /api-reference/openapi.json get /api/tenant/ai # Updates the AI provider configuration for the current tenant. Hidden from the SDK/CLI because the payload carries provider API keys; AI config is tenant infrastructure, not customer data ops or migration scope. Configure AI through the admin UI instead. Source: https://ekso.dev/api-reference/tenant/updates-the-ai-provider-configuration-for-the-current-tenant-hidden-from-thesdkcli-because-the-payload-carries-provider-api-keys;-ai-config-is-tenantinfrastructure-not-customer-data-ops-or-migration-scope-configure-ai-throughthe-admin-ui-instead /api-reference/openapi.json put /api/tenant/ai # Updates the current tenant's profile information such as name, locale, and admin contact details. Source: https://ekso.dev/api-reference/tenant/updates-the-current-tenants-profile-information-such-as-name-locale-and-admin-contact-details /api-reference/openapi.json put /api/tenant # Creates a new non-billable time entry. Source: https://ekso.dev/api-reference/time/creates-a-new-non-billable-time-entry /api-reference/openapi.json post /api/time # Deletes a non-billable time entry by its identifier. Source: https://ekso.dev/api-reference/time/deletes-a-non-billable-time-entry-by-its-identifier /api-reference/openapi.json delete /api/time/{id} # Lists items with billable time logged by a user within a date range. Source: https://ekso.dev/api-reference/time/lists-items-with-billable-time-logged-by-a-user-within-a-date-range /api-reference/openapi.json post /api/time/resource/{userId} # Lists non-billable time entries for a user within a date range. Source: https://ekso.dev/api-reference/time/lists-non-billable-time-entries-for-a-user-within-a-date-range /api-reference/openapi.json post /api/time/{id} # Updates an existing non-billable time entry. Source: https://ekso.dev/api-reference/time/updates-an-existing-non-billable-time-entry /api-reference/openapi.json put /api/time/{id} # Creates a new watch (pin or subscription) on an entity for a watcher. Source: https://ekso.dev/api-reference/watch/creates-a-new-watch-pin-or-subscription-on-an-entity-for-a-watcher /api-reference/openapi.json post /api/watch # Deletes a watch by its identifier. Source: https://ekso.dev/api-reference/watch/deletes-a-watch-by-its-identifier /api-reference/openapi.json delete /api/watch/{id} # Lists all watches created by a specific watcher (e.g. User, Email, User Group). Source: https://ekso.dev/api-reference/watch/lists-all-watches-created-by-a-specific-watcher-eg-user-email-user-group /api-reference/openapi.json get /api/watch/watcher/{watcherId} # Lists all watches for a specific owner entity (e.g. Container, Board, Item). Source: https://ekso.dev/api-reference/watch/lists-all-watches-for-a-specific-owner-entity-eg-container-board-item /api-reference/openapi.json get /api/watch/owner/{ownerId} # Retrieves a single watch by its identifier. Source: https://ekso.dev/api-reference/watch/retrieves-a-single-watch-by-its-identifier /api-reference/openapi.json get /api/watch/{id} # Updates an existing watch by its identifier. Source: https://ekso.dev/api-reference/watch/updates-an-existing-watch-by-its-identifier /api-reference/openapi.json put /api/watch/{id} # Authentication Source: https://ekso.dev/cli/authentication Sign in via OAuth 2.0 device flow for interactive use, or pass an API key for non-interactive automation. ## Two paths The CLI accepts two kinds of credentials: | Path | Best for | How | | --------------------- | ------------------------------------------------------------------- | ------------------------------------------------ | | **Device-flow OAuth** | Humans at a terminal. Tokens auto-refresh; no manual rotation. | `ekso auth login --url ` | | **API key** | CI, agents, scripts, anything headless. Static; rotate on a policy. | `--api-key ` flag or `EKSO_API_KEY` env var | Both produce the same authenticated session against your install. The backend stamps each request with a `Client` marker (`Cli` for device-flow tokens, `Sdk` for API keys) to gate operations that should not be scriptable — see [CLI/SDK marker](/sdk/cli-sdk-marker). ## Device-flow login Run: ```bash theme={null} ekso auth login --url https://ekso.acme.com ``` The CLI prints a short code and a verification URL, opens your browser to that URL, and polls in the background. Approve the request in the browser and the CLI captures an access + refresh token pair. They're stored in the credential store keyed on the install URL, so subsequent `ekso ...` calls skip the login step. ```bash theme={null} $ ekso auth login --url https://ekso.acme.com Open https://ekso.acme.com/auth/device and enter code: ABCD-1234 Waiting for approval... ✓ Authenticated to https://ekso.acme.com ``` Tokens auto-refresh on every request — when the access token nears expiry, the SDK exchanges the refresh token for a new pair and rewrites the credential store transparently. You should not need to re-run `auth login` until the refresh token itself expires (\~7 days of inactivity). ## API-key auth API keys live in the admin surface — mint one with `ekso api-key create --name "ci-deploy"`. The response contains the raw key **exactly once** (`ek_...`); store it immediately. Pass it on every call via `--api-key`: ```bash theme={null} ekso item list --url https://ekso.acme.com --api-key ek_live_xxx ``` Or — typically cleaner — set the env var: ```bash theme={null} export EKSO_URL=https://ekso.acme.com export EKSO_API_KEY=ek_live_xxx ekso item list ``` Keys carry the permissions of the user who minted them. Revoke with `ekso api-key delete ` (the key stops working immediately). ## Precedence When both are present, **`--api-key` (or `EKSO_API_KEY`) wins**. Stored device-flow tokens are ignored. This makes CI overrides simple — set `EKSO_API_KEY` in the runner and the same `ekso ...` invocations a developer uses locally now run headless. The full resolution order, highest priority first: 1. `--api-key ` flag 2. `EKSO_API_KEY` environment variable 3. Stored device-flow credentials for `--url`/`EKSO_URL` 4. Otherwise — exit code `3` (auth error) with a "run `ekso auth login`" hint. ## Logging out ```bash theme={null} ekso auth logout --url https://ekso.acme.com ``` Clears stored tokens for that install. The credential store is keyed on URL, so logging out of one install doesn't affect other installs you may also have signed into (your own + a customer's, say). ## Local development If you're running the backend locally (e.g. on `https://devinc.localhost:7070`), pass that URL directly: ```bash theme={null} export EKSO_URL=https://devinc.localhost:7070 ekso auth login ``` Stored credentials are scoped to the install URL, so dev and prod creds don't collide. # Command branches Source: https://ekso.dev/cli/branches Every top-level CLI branch at a glance. Each branch maps to a resource in the Ekso API. ## How branches work Every CLI command lives under a top-level **branch** that names a resource — `ekso item ...`, `ekso field ...`, `ekso docs ...`. Verbs scope to that resource (`list`, `get`, `create`, `update`, `delete`) plus any resource-specific extras (`field create-list`, `docs folder create`). You can always discover the verbs for a branch with: ```bash theme={null} ekso --help ``` And the flags for a verb with: ```bash theme={null} ekso --help ``` `--help` reflects the binary you have installed — treat it as the source of truth if these docs and your CLI ever drift. ## Work management | Branch | What it does | | ----------- | ------------------------------------------------------------------------------------ | | `item` | Items — the core unit of work. Create, list, filter, copy, screen, dependency edges. | | `container` | Containers — top-level groupings (products, projects, support queues). | | `area` | Sub-groupings within a container (front-end, database, complaints, …). | | `label` | Coloured labels you attach to containers and items. | | `link` | Item-to-item references (`Related`, `Duplicate`, …). | | `watch` | Watchers — users who get notified on item changes. | ## Process & workflow | Branch | What it does | | ------------ | ------------------------------------------------------------------------------------------- | | `process` | Business processes — define which fields and verbs an item uses. | | `field` | Custom and system field definitions. One verb per type — `create-date`, `create-list`, etc. | | `constraint` | Field validation rules (regex, ranges, required-when conditions). | | `rule` | Automation rules — `when X happens, do Y`. | | `screen` | Item-detail screens — which fields render in which order. | | `clock` | SLA clocks — countdown timers with start/finish/breach status. | ## Planning & delivery | Branch | What it does | | ------- | ---------------------------------------------------------------- | | `board` | Boards — Kanban / table / timeline views over a container. | | `cycle` | Cycles (sprints) — time-boxed groupings of items within a board. | ## Time & finance | Branch | What it does | | --------------- | ------------------------------------------------------------ | | `time` | Time entries — log work against items, query by user/period. | | `cost-center` | Cost centers users belong to. | | `job-role` | Job roles + billing rates. | | `sku` | Stock-unit / product codes for billable work. | | `crm` | CRM records — clients items can be tagged against. | | `non-billable` | Non-billable categories (Meetings, Training, Vacation). | | `budget-reason` | Reason codes for budget overruns / under-runs. | | `reporting` | Reports — profitability, risk, time summaries. | ## Knowledge & files | Branch | What it does | | ------ | ---------------------------------------------- | | `docs` | Knowledge-base folders, docs, and doc content. | | `file` | File attachments on items and annotations. | ## Communication | Branch | What it does | | ---------------- | ----------------------------------------------------------- | | `mail-general` | Tenant-wide mail config (block lists, suppressed subjects). | | `mail-mailbox` | Mailbox connections — IMAP/POP credentials. | | `mail-ticketing` | Ticketing pipelines that turn inbound email into items. | | `notification` | Notification preferences and history. | | `hook` | Outbound webhooks fired on rule actions. | ## Administration | Branch | What it does | | ---------------- | -------------------------------------------------- | | `auth` | Sign in / out, current-user details. | | `user` | Tenant users. Admin-tier — requires SuperAdmin. | | `user-group` | User groups for permissions. Admin-tier. | | `access-control` | Role grants (View / Add / Manage / …). Admin-tier. | | `api-key` | Mint, list, revoke API keys. Admin-tier. | | `meta` | Meta data — countries, currencies, timezones. | | `mcp` | MCP-server-related operations. | ## Migration | Branch | What it does | | --------- | -------------------------------------------------------------------------------------------------------------------------- | | `migrate` | Move data from Jira, Linear, Azure DevOps, Zendesk, or Countersoft Gemini into Ekso. See [Migrate](/cli/migrate/overview). | ## Utility | Command | What it does | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `completion` | Print a shell-completion script for `bash`, `zsh`, `fish`, or `pwsh`. See [Shell completion](/cli/installation#shell-completion) in the installation guide. | ## See also * [Authentication](/cli/authentication) — how `ekso auth login` and API keys work. * [Configuration](/cli/configuration) — the global flag and env-var surface every branch shares. * [API Reference](/api-reference/introduction) — the underlying HTTP routes each command calls. # Configuration Source: https://ekso.dev/cli/configuration Global flags, environment variables, output formats, and POSIX exit codes. ## Global flags Every `ekso` command accepts these flags. They sit alongside command-specific options: | Flag | Description | Env var | Default | | ----------------------- | ------------------------------------------------------------------------------------------------- | -------------- | ------- | | `--url ` | Absolute URL of your Ekso install (e.g. `https://ekso.acme.com`). Required unless env-var is set. | `EKSO_URL` | — | | `--api-key ` | API key for non-interactive auth. Overrides any stored device-flow tokens. | `EKSO_API_KEY` | — | | `-f, --format ` | Output format. One of `json`, `jsonl`, `yaml`, `table`. | `EKSO_FORMAT` | `json` | | `-q, --quiet` | Suppress non-essential output. On `create`, prints just the new id. | — | off | | `-v, --verbose` | Verbose logging — request/response details. | — | off | | `--no-color` | Disable ANSI colors. Honours [NO\_COLOR](https://no-color.org). | `NO_COLOR` | off | | `--timeout ` | HTTP timeout in seconds. | `EKSO_TIMEOUT` | `30` | Flags always win over env vars. Env vars are convenient for shell sessions and CI runners — set once, every subsequent `ekso ...` call inherits them. Self-host: there is no concept of tenant subdomain. The install URL — whatever `PublicUrl` you set during the `/startup` wizard — is the single addressable identity, and the CLI talks to it directly. ## Recommended shell setup Drop these in your `.zshrc` / `.bashrc` so you don't repeat yourself: ```bash theme={null} # Production install export EKSO_URL=https://ekso.acme.com # CI/agent — leave unset for local dev (uses cached device-flow tokens) # export EKSO_API_KEY=ek_live_xxx ``` For local backend development: ```bash theme={null} export EKSO_URL=https://devinc.localhost:7070 ``` ## Output formats The CLI defaults to **JSON** — the same shape Ekso returns over HTTP. Use `--format` (or `EKSO_FORMAT`) to change: | Format | Use case | | ---------------- | ------------------------------------------------------------------ | | `json` (default) | Machine-readable, pipes cleanly into `jq`. | | `jsonl` | Newline-delimited JSON. One record per line. Good for streaming. | | `yaml` | Human-readable for browsing, terraform-style configs. | | `table` | Compact aligned columns for terminal viewing. Loses nested fields. | ```bash theme={null} ekso item list --format table ``` `-q, --quiet` is independent of `--format` — it suppresses success messages and non-essential prose, leaving only the data (or just the id, on create). Combine `-q --format json` for clean machine output. ## Exit codes The CLI follows POSIX conventions plus a few Ekso-specific codes: | Code | Meaning | | ---- | --------------------------------------------------------------- | | `0` | Success | | `1` | Generic error (unknown failure) | | `2` | Usage error (bad flags / missing required argument) | | `3` | Auth error (no credentials, expired refresh token, revoked key) | | `4` | Forbidden (insufficient permissions, e.g. not a super admin) | | `5` | Not found (HTTP 404) | | `6` | Validation error (HTTP 400 / 422) | | `7` | Rate limited (HTTP 429) — back off and retry | | `8` | Network error (DNS, TCP, TLS, timeout) — typically transient | | `9` | Server error (HTTP 5xx) | Branch on these in shell pipelines: ```bash theme={null} ekso item create --name "Bug" --url https://ekso.acme.com case $? in 0) echo "ok" ;; 3) echo "re-authenticate" ; ekso auth login --url https://ekso.acme.com ;; 7) echo "rate limited" ; sleep 30 ;; *) echo "failed: exit $?" ;; esac ``` ## Help Every command and branch has built-in help: ```bash theme={null} ekso --help # Top-level: list of branches ekso item --help # Branch: list of verbs ekso item create --help # Verb: flags + arguments ``` `--help` prints the same surface as the docs but reflects the binary you have installed — it's the source of truth if the rendered docs and the installed CLI ever drift. # Installation Source: https://ekso.dev/cli/installation Install the Ekso CLI — one self-contained binary, no runtime needed. The Ekso CLI is a single self-contained binary — **no .NET runtime, no package manager, no setup**. Download one file, run it. Releases live at [github.com/EksoHQ/CLI/releases](https://github.com/EksoHQ/CLI/releases). One archive per platform, \~30 MB each. ## Install ```bash theme={null} VERSION=$(curl -sS https://api.github.com/repos/EksoHQ/CLI/releases/latest | grep tag_name | cut -d'"' -f4 | sed 's/^v//') curl -L "https://github.com/EksoHQ/CLI/releases/download/v$VERSION/ekso-$VERSION-osx-arm64.tar.gz" | tar xz xattr -d com.apple.quarantine ./ekso # one-time: clears Gatekeeper sudo mv ./ekso /usr/local/bin/ ekso --version ``` ```bash theme={null} VERSION=$(curl -sS https://api.github.com/repos/EksoHQ/CLI/releases/latest | grep tag_name | cut -d'"' -f4 | sed 's/^v//') curl -L "https://github.com/EksoHQ/CLI/releases/download/v$VERSION/ekso-$VERSION-osx-x64.tar.gz" | tar xz xattr -d com.apple.quarantine ./ekso sudo mv ./ekso /usr/local/bin/ ekso --version ``` ```bash theme={null} VERSION=$(curl -sS https://api.github.com/repos/EksoHQ/CLI/releases/latest | grep tag_name | cut -d'"' -f4 | sed 's/^v//') curl -L "https://github.com/EksoHQ/CLI/releases/download/v$VERSION/ekso-$VERSION-linux-x64.tar.gz" | tar xz sudo mv ./ekso /usr/local/bin/ ekso --version ``` ```bash theme={null} VERSION=$(curl -sS https://api.github.com/repos/EksoHQ/CLI/releases/latest | grep tag_name | cut -d'"' -f4 | sed 's/^v//') curl -L "https://github.com/EksoHQ/CLI/releases/download/v$VERSION/ekso-$VERSION-linux-arm64.tar.gz" | tar xz sudo mv ./ekso /usr/local/bin/ ekso --version ``` 1. Download `ekso--win-x64.zip` from the [latest release](https://github.com/EksoHQ/CLI/releases/latest). 2. Extract `ekso.exe` into a folder on your `PATH`, or into a new folder that you add to your `PATH`. 3. Run `ekso --version` from a new terminal. On first run, Windows SmartScreen may show **"Windows protected your PC"**. Click **More info → Run anyway**. That's it. ### Update Re-run the install command in your platform's tab — download the latest archive and overwrite the existing binary. If you installed via the .NET global tool (below), use `dotnet tool update -g Ekso.Cli` instead. Either way, you always end up on the latest release. ### macOS Gatekeeper Binaries aren't yet signed with an Apple Developer ID, so Gatekeeper quarantines them on first download. The `xattr -d com.apple.quarantine ./ekso` line in the macOS install steps clears it. If you see *"ekso cannot be opened because the developer cannot be verified"*, run that command against the downloaded binary, or right-click → **Open** in Finder once. Code signing is on the roadmap. ### Verify the download Each release ships a `SHA256SUMS` file alongside the archives: ```bash theme={null} curl -LO "https://github.com/EksoHQ/CLI/releases/download/v$VERSION/SHA256SUMS" shasum -a 256 -c SHA256SUMS --ignore-missing ``` *** ## Alternative: .NET global tool Already have .NET 10 installed and prefer the dotnet-tool ecosystem? The CLI is also published to NuGet: ```bash theme={null} dotnet tool install -g Ekso.Cli ekso --version ``` This is intended for .NET developers who manage CLIs through `dotnet tool` already. Everyone else: use the [standalone binary](#install) above. Update with `dotnet tool update -g Ekso.Cli`. All versions, including pre-releases: [nuget.org/packages/Ekso.Cli](https://www.nuget.org/packages/Ekso.Cli). *** ## Shell completion The `ekso` binary self-emits a completion script for your shell. There's no extra file in the tarball — the binary is the install vector. Pick your shell, run the matching command once, and re-run after each upgrade so newly-added commands autocomplete. ```bash theme={null} mkdir -p ~/.zsh/completion ekso completion zsh > ~/.zsh/completion/_ekso # If your ~/.zshrc doesn't already source ~/.zsh/completion, add: # fpath=(~/.zsh/completion $fpath) # autoload -Uz compinit && compinit # If completion doesn't kick in immediately, clear the compinit cache: rm -f ~/.zcompdump* ``` ```bash theme={null} # Requires the bash-completion package on most distros. ekso completion bash | sudo tee /etc/bash_completion.d/ekso > /dev/null # Or, no-sudo install scoped to your shell: ekso completion bash > ~/.local/share/bash-completion/completions/ekso ``` ```bash theme={null} brew install bash-completion@2 ekso completion bash > "$(brew --prefix)/etc/bash_completion.d/ekso" ``` macOS ships zsh by default since 10.15 — only do this if you've explicitly switched to bash. ```bash theme={null} ekso completion fish > ~/.config/fish/completions/ekso.fish ``` fish picks the file up automatically — no `source` step required. ```powershell theme={null} # Persistent — appended to your PowerShell profile so every new shell loads it. ekso completion pwsh >> $PROFILE # Or for the current session only: ekso completion pwsh | Out-String | Invoke-Expression ``` Works in both Windows PowerShell 5.1 and PowerShell 7+. ### Ephemeral install (current shell only) For a quick try without writing to disk: ```bash theme={null} source <(ekso completion bash) # bash source <(ekso completion zsh) # zsh ``` ### What completes After installing, `` completes: * Top-level branches — `ekso ` → `auth item board mcp migrate ...` * Verbs within a branch — `ekso item ` → `create get list update delete` * Nested branches — `ekso docs folder ` → `list get create update delete`, `ekso migrate jira ` → `list-projects collect apply status` * Global flags after `-` — `ekso item list --` → `--url --api-key --format ...` Per-flag value completion (item ids, etc.) is on the roadmap. For now, `` completes commands and flag *names*; values you type yourself. ### Re-run after upgrade The completion script is a snapshot of the binary's command surface at the moment you ran `ekso completion `. New commands added by a later release won't autocomplete until you re-run the same command and overwrite the script. ## Next steps * **[Authenticate](/cli/authentication)** — sign in to your install via device flow, or supply an API key. * **[Configure](/cli/configuration)** — set defaults via environment variables so you don't repeat `--url` on every call. # Before you start Source: https://ekso.dev/cli/migrate/before-you-start Prerequisites, secrets policy, and token rotation for ekso migrate. A short checklist before you run any `ekso migrate` command. None of these are optional. ## Prerequisites You need: 1. **An Ekso install.** If you don't have one yet, see [ekso.app/pricing](https://ekso.app/pricing) for license options. Stand up the install (Docker is the supported path) and complete the `/startup` wizard so you have a `PublicUrl` (e.g. `https://ekso.acme.com`) you can hit. 2. **An Ekso API key with admin scope.** If you have the Ekso CLI installed and you're authenticated as a Super Admin, run: ```bash theme={null} ekso api-key create --name "Migration key" --url ``` `ekso migrate` writes containers, items, users, files, and labels, so the key needs admin write permissions for the duration of the run. Rotate or delete the key when the migration finishes. 3. **A destination Ekso process.** The migrator writes every item to a single `DataProcess` you choose at apply time via `--process `. Create the process in Ekso first (or pick an existing one). The Apply layer auto-creates any custom fields you reference in your field-map; system fields (Status, Priority, Severity, etc.) must already exist on the process. 4. **A destination Ekso board.** Cycles need a board to attach to. Pass `--board ` at apply time, or let the CLI auto-discover when the install has exactly one board. 5. **Read access to the source platform.** Whatever credential type the source needs — API token, PAT, SQL user, GraphQL key. See the per-source page for shape: * [Jira](/cli/migrate/jira) — Cloud: email + API token; Data Center/Server: Personal Access Token (PAT) * [Linear](/cli/migrate/linear) — Personal API key * [Azure DevOps](/cli/migrate/devops) — Personal Access Token (PAT) * [Zendesk](/cli/migrate/zendesk) — Email + API token * [Gemini](/cli/migrate/gemini) — SQL connection string *or* API key 6. **The Ekso CLI itself.** Run `ekso --version` to confirm. If it's not installed, follow [Installation](/cli/installation). ## The configuration file Every `ekso migrate` command takes `--config ` (default: `./migration.config.json`). This is the file that holds your source-platform credentials. Start from the sample shipped with the CLI source tree: ```json theme={null} { "_comment": "Copy this to migration.config.json and fill in real values. The working file is gitignored. Do not commit secrets.", "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." }, "source": "jira", "jira": { "url": "https://acme.atlassian.net", "username": "you@acme.com", "apiToken": "ATATT3..." }, "linear": { "apiKey": "lin_api_..." }, "devops": { "organisation": "your-org", "personalAccessToken": "..." }, "zendesk": { "url": "https://acme.zendesk.com", "email": "you@acme.com", "apiToken": "..." }, "gemini": { "connectionMode": "api", "connectionString": "Server=gemini-db;Database=Gemini761;TrustServerCertificate=True;", "url": "https://gemini.acme.local", "username": "manager", "apiKey": "..." }, "fieldMap": "migration.fields.yaml", "attachments": { "import": "db" } } ``` You only need the block for the source you're migrating from — the others can stay as placeholders. ## Secrets policy This file holds live credentials for both the source platform *and* your Ekso install. Treat it like any other secret: * **Never commit `migration.config.json`.** Add it to your `.gitignore`. The Ekso repo's own gitignore already blocks `migration.config.json`, `*.migration.json`, and `migration.*.json` patterns. * **Use environment variables for CI/CD.** Each per-source token has an env-var override that takes precedence over the file: | Source | Env var | Overrides | | ------------------------- | ------------------------------------------------------- | ------------------------------------------------------ | | Jira (Cloud) | `EKSO_MIGRATE_JIRA_TOKEN` | `jira.apiToken` | | Jira (Data Center/Server) | `EKSO_MIGRATE_JIRA_PAT` | `jira.personalAccessToken` | | Linear | `EKSO_MIGRATE_LINEAR_KEY` | `linear.apiKey` | | Azure DevOps | `EKSO_MIGRATE_DEVOPS_PAT` | `devops.personalAccessToken` | | Zendesk | `EKSO_MIGRATE_ZENDESK_TOKEN` | `zendesk.apiToken` | | Gemini | `EKSO_MIGRATE_GEMINI_KEY`, `EKSO_MIGRATE_GEMINI_DB_PWD` | `gemini.apiKey`, password in `gemini.connectionString` | In CI, set the env vars and ship a config file with placeholder strings. The CLI reads env first, file second. * **Rotate source-platform tokens after the migration finishes.** A migration credential needs read-everything scope on the source; that's a lot of authority to leave hanging around. Rotate the source-platform credential as soon as the apply phase succeeds. The Ekso API key can stay (or rotate it too — your call). * **The cache file is local-only.** `~/.ekso/migrate/-.sqlite` holds the data you fetched from the source platform — descriptions, comment bodies, attachment bytes. Treat the cache file the same way you'd treat the source data itself: don't paste it into a public bug tracker, don't commit it, delete it once apply has succeeded. ## What the cache contains The SQLite cache holds raw source data — every comment body, every attachment blob, every assignee email. It is **not** encrypted at rest. If you're handling a regulated data set, run the migration on an encrypted volume and delete the cache file once apply has succeeded. The cache does **not** contain credentials. Tokens stay in the config file or environment, never in the SQLite file. ## Verifying you're set up `collect` itself preflights your Ekso credentials with a single cheap API call before pulling any source data — wrong URL or API key fails fast with **exit 3** in under a second, not after a long collect run. So you no longer need a separate sanity-check step on the Ekso side. If you also want to verify your **source-platform** credentials before kicking off a real `collect`, the per-source list commands make a read-only call: ```bash theme={null} # Jira — should print your accessible projects ekso migrate jira list-projects --config migration.config.json --url https://ekso.acme.com # Linear — should print your projects ekso migrate linear list-projects --config migration.config.json --url https://ekso.acme.com # Azure DevOps — should print your projects ekso migrate devops list-projects --config migration.config.json --url https://ekso.acme.com # Zendesk — should print your orgs (or empty if your instance is flat) ekso migrate zendesk list-orgs --config migration.config.json --url https://ekso.acme.com # Gemini — should print your projects ekso migrate gemini list-projects --config migration.config.json --url https://ekso.acme.com ``` If any of those fail with an auth error (exit code `3`), fix the credentials before continuing. If they work, you're ready for `collect`. ## Where to next * **[Command reference](/cli/migrate/command-reference)** — every command, every flag. * **[Field mapping](/cli/migrate/field-mapping)** — write a `migration.fields.yaml` for your custom fields. * Pick your source: [Jira](/cli/migrate/jira) / [Linear](/cli/migrate/linear) / [Azure DevOps](/cli/migrate/devops) / [Zendesk](/cli/migrate/zendesk) / [Gemini](/cli/migrate/gemini). # Command reference Source: https://ekso.dev/cli/migrate/command-reference Every ekso migrate command and flag, in one place. The canonical reference for every `ekso migrate` command. Each per-source page links here for the global flag surface so the source-specific pages can stay focused on the source-specific quirks. ## The four commands Every source supports the same four verbs: | Command | What it does | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `list-*` | Read-only. Enumerate the source containers (`projects`, `teams`, `orgs`) the configured credentials can see. Useful for verifying setup. | | `collect` | Pull source data into a local SQLite cache. Network: source-only. No Ekso writes. | | `apply` | Walk the cache and write to the Ekso REST API. Network: Ekso-only. Idempotent — re-runs skip already-applied rows. | | `status` | Read the most recent cache file and print row counts per table, plus IdMap progress. No network. | The `list-*` verb is named per-source to match the customer's mental model: * `ekso migrate jira list-projects` * `ekso migrate linear list-projects` * `ekso migrate devops list-projects` * `ekso migrate zendesk list-projects` * `ekso migrate gemini list-projects` ## Global flags (every migrate command) These flags work on every `ekso migrate ` invocation, alongside the standard CLI flags from [Configuration](/cli/configuration) (`--url`, `--api-key`, `--format`, `--quiet`, etc.). | Flag | Description | Default | | --------------------- | --------------------------------------------------------------------- | ------------------------- | | `--config ` | Path to the migration config JSON. Holds source-platform credentials. | `./migration.config.json` | | `--working-dir ` | Where to put the SQLite cache. | `~/.ekso/migrate/` | ## `collect` flags | Flag | Description | Required? | | --------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `--project ` | Source container to collect. Repeat for multiple. | Required for Jira / Linear / DevOps / Gemini; optional for Zendesk (omit to collect every organisation under the configured Zendesk subdomain). | | `--no-attachments` | Skip downloading binary attachments. Smaller and faster cache. | No | | `--no-comments` | Skip fetching comments. | No | | `--exclude-closed` | Filter closed items at the source-API layer (Jira, Zendesk, Gemini). | No | | `--resume` | Resume an interrupted collect. Skips containers already cached. | No | | `--edition ` | (Jira only) Override Jira edition detection (default: auto-detect via serverInfo). | No | | `--connection-mode ` | (Gemini only) `sql` or `api`. Overrides `gemini.connectionMode` in the config. | No | ## `apply` flags | Flag | Description | Required? | | ---------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | `--process ` | Destination Ekso process. Every migrated item attaches to this process. | **Yes** | | `--board ` | Destination board. Cycles attach here. Auto-discovered when the tenant has exactly one board. | If multiple boards | | `--field-map ` | YAML file mapping source custom fields to Ekso process fields. See [Field mapping](/cli/migrate/field-mapping). | No | | `--user-strategy ` | Identity resolution: `match-or-create`, `match-only`, `migration-bot`. | No (default: `match-or-create`) | | `--fallback-user ` | User ID used by `match-only` and `migration-bot` when there's no source match. | If using `match-only` or `migration-bot` | | `--dry-run` | Walk the cache and log what would happen without writing to Ekso. | No | | `--resume` | Skip rows that already have an ID-map entry. Picks up where a failed apply left off. | No | | `--cache ` | Path to a specific cache file. Default: most recent `-*.sqlite` in the working dir. | No | See [Identity resolution](/cli/migrate/identity-resolution) for what each `--user-strategy` does. ## `status` flags `status` is read-only and has no source-specific flags. It reads the most recent cache file (or `--cache `) and prints: * Schema version of the cache * Row counts per `Source*` table (issues, comments, attachments, etc.) * IdMap progress per entity kind (how many items / users / files have been applied) * Whether the cache is fresh (collect-only) or partially / fully applied ## A typical run ```bash theme={null} # 1. Verify credentials work — read-only. ekso migrate jira list-projects --config migration.config.json --url https://ekso.acme.com # 2. Collect from the source. Writes ~/.ekso/migrate/jira-2026-04-29T143205.sqlite. ekso migrate jira collect \ --config migration.config.json \ --project ACME \ --url https://ekso.acme.com # 3. Dry-run the apply. Walks the cache; no Ekso writes. ekso migrate jira apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com \ --dry-run # 4. Apply for real. Writes to your Ekso tenant. ekso migrate jira apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com # 5. (If anything fails) re-run with --resume. Picks up at the last successful row. ekso migrate jira apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com \ --resume # 6. Inspect the cache after the fact. ekso migrate jira status --config migration.config.json ``` ## Idempotency model Every `apply` writes (source-id → Ekso-id) pairs into an `IdMap` table inside the cache. A re-run reads the IdMap before each write and skips rows that already have an Ekso ID. This means: * Running `apply` twice in a row is a no-op the second time. * A killed `apply` (Ctrl-C, network blip, machine reboot) can resume cleanly with `--resume`. * Running `apply` without `--resume` after a partial run is still safe — it just re-checks every row and skips the applied ones. `--resume` skips the per-row IdMap lookup for speed. If you want to start from scratch, delete the cache file (or run a fresh `collect` — each `collect` writes a new timestamped file). ## Per-cache schema versioning Every cache file is stamped with a `schema_version`. If you upgrade the CLI between `collect` and `apply` and the schema version changes, `apply` refuses to run and tells you to re-collect. This is intentional — silent data shape mismatches are worse than an explicit "please re-collect" error. ## Where to next * **[Identity resolution](/cli/migrate/identity-resolution)** — pick the right `--user-strategy`. * **[Field mapping](/cli/migrate/field-mapping)** — write `migration.fields.yaml`. * **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and recovery. * Per-source: [Jira](/cli/migrate/jira) / [Linear](/cli/migrate/linear) / [Azure DevOps](/cli/migrate/devops) / [Zendesk](/cli/migrate/zendesk) / [Gemini](/cli/migrate/gemini). # Migrate from Azure DevOps Source: https://ekso.dev/cli/migrate/devops Move Azure DevOps work items, comments, attachments, links, and iterations into Ekso — in two commands. `ekso migrate devops` pulls work items, comments, attachments, links, and iterations from Azure DevOps (and on-prem Azure DevOps Server / TFS) into your Ekso tenant. Two commands — `collect` and `apply`. Read [Migrate overview](/cli/migrate/overview) and [Before you start](/cli/migrate/before-you-start) first. ## What gets imported | DevOps concept | Ekso shape | | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | Team Project | [`DataContainer`](/data-model/data-container) (`Code = ProjectName`, project GUID in `Meta.devops_project_id`) | | Work Item (Bug, Task, User Story, Feature, Epic, Issue, …) | [`DataItem`](/data-model/data-item) | | Discussion comment | [`DataAnnotation`](/data-model/data-annotation) | | Attachment | [`DataFile`](/data-model/data-file) (multipart upload) | | Link (Parent, Child, Related, Successor, Predecessor, Tested By, …) | [`DataLink`](/data-model/data-link) | | Iteration (Sprint) | [`DataCycle`](/data-model/data-cycle) | | Area Path | [`ConfigLabel`](/data-model/config-label) (prefix `area:`) + `DataItem.Tags` | | Tag | [`ConfigLabel`](/data-model/config-label) | | Identity (DevOps user) | [`DataUser`](/data-model/data-user) (matched or minted) | | `System.WorkItemType` | `DataItem.Meta.devops_workitemtype` | | `Microsoft.VSTS.Scheduling.StoryPoints` | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) | | Custom field (`Custom.`) | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) or `Meta` | Authoring fidelity preserved — every comment, every change is attributed to the original DevOps identity (matched by email). ## What does NOT get imported * **Revision history.** Each work item has full edit history in DevOps; v1 imports only the current state. Re-evaluate if a customer needs SOX/audit migration. * **State transition rules.** The migrator snapshots the *current* state of each item; transition rules don't transfer. * **Build & release pipelines.** Out of scope. * **Repos / Git history.** Out of scope — work items only. * **Test plans / suites / runs.** Test management is out of scope for v1. * **Boards & queries (saved WIQL).** Re-author on the Ekso side. * **Service identities** (`[Project]\Project Collection Build Service`, etc.) are filtered at collect — they're not real users. ## Before you start Pre-flight checklist on top of [the general one](/cli/migrate/before-you-start): * An Azure DevOps organisation (`https://dev.azure.com/`). * A [Personal Access Token (PAT)](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate) with **Work Items (Read)** + **Identity (Read)** scopes. Add a `devops` block to your `migration.config.json`: ```json theme={null} { "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." }, "source": "devops", "devops": { "organisation": "your-org", "personalAccessToken": "..." } } ``` Or use the env-var override `EKSO_MIGRATE_DEVOPS_PAT` for CI use cases. The `organisation` field is the segment after `dev.azure.com/` in your DevOps URL. For on-prem Azure DevOps Server, use the full base URL — see the on-prem section below. ## Step 1 — list projects ```bash theme={null} ekso migrate devops list-projects \ --config migration.config.json \ --url https://ekso.acme.com ``` Sample output: ``` NAME ID STATE Platform 01a3b9c8-1f44-4eaa-9c6e-7d5b2c1d3f4e wellFormed Mobile 02b4ca99-2055-5fbb-ad7f-8e6c3d2e4a5f wellFormed ``` ## Step 2 — collect ```bash theme={null} ekso migrate devops collect \ --config migration.config.json \ --project Platform \ --url https://ekso.acme.com ``` Repeat `--project` for multiple. Sample run: ``` fetching projects... 1 fetched running WIQL query... 4127 work items found fetching work items... 4127/4127 (chunked at 200) ok fetching discussion comments... 9803/9803 ok fetching attachments... 421/421 ok fetching iterations... 23 iterations ok saved cache: ~/.ekso/migrate/devops-2026-04-29T143205.sqlite ``` Useful flags: * `--no-attachments` — skip downloads. * `--no-comments` — skip discussion. * `--resume` — pick up where a killed collect left off. ## Step 3 — dry-run apply ```bash theme={null} ekso migrate devops apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com \ --dry-run ``` ## Step 4 — apply for real ```bash theme={null} ekso migrate devops apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com ``` If interrupted, re-run with `--resume`. ## Identity resolution for DevOps DevOps identities have email — `--user-strategy match-or-create` is reliable. Service identities (`[Project]\Project Collection Build Service`, `[Acme]\Project Collection Service Accounts`, etc.) are filtered out at collect time — they aren't real users and don't pollute your Ekso user list. There's a regression test pinning this. Read [Identity resolution](/cli/migrate/identity-resolution). ## Custom fields for DevOps DevOps fields use `Reference Name` identifiers like `Microsoft.VSTS.Scheduling.StoryPoints` or `Custom.`: ```yaml theme={null} devops: Microsoft.VSTS.Scheduling.StoryPoints: { ekso: StoryPoints, kind: decimal } Microsoft.VSTS.Common.Severity: ekso: Severity kind: picker picker: "1 - Critical": P0 "2 - High": P1 "3 - Medium": P2 "4 - Low": P3 Custom.RootCause: { ekso: RootCause, kind: text } ``` Discover your reference names: ```bash theme={null} curl -u :PAT \ https://dev.azure.com/your-org/your-project/_apis/wit/fields?api-version=7.1 \ | jq '.value[] | {referenceName, name, type}' ``` Pass it to apply: ```bash theme={null} ekso migrate devops apply \ --config migration.config.json \ --process proc_engineering \ --field-map migration.fields.yaml ``` See [Field mapping](/cli/migrate/field-mapping). ## Iterations, Area Paths, Work Item Types | DevOps | Ekso | Notes | | ------------------------------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | Iteration | `DataCycle` | Hierarchy flattens in v1. `MyProj\Sprint 1\Week 1` becomes a cycle named `Sprint 1 / Week 1`. Hierarchical cycle support is on the v2 roadmap. | | Area Path | `ConfigLabel` (`area:Backend/API`) + `DataItem.Tags` | Slash-joined label preserves the path. | | Work Item Type (Bug, Task, Story, Feature, Epic) | `DataItem.Meta.devops_workitemtype` | A single Ekso process can hold all work item types. The original type is preserved in `Meta` for audit. | Cycles attach to a board on the Ekso side. Pass `--board ` if the tenant has more than one board. ## On-prem Azure DevOps Server (TFS) The same CLI works against on-prem Azure DevOps Server / TFS — point the `organisation` at the full base URL of your collection: ```json theme={null} "devops": { "organisation": "https://tfs.acme.local/tfs/DefaultCollection", "personalAccessToken": "..." } ``` Collection-scoped PATs work the same way as Azure DevOps Service PATs. The Microsoft TFS SDK bundled with the CLI handles both. ## Iron rule — WIQL chunking and 429 retry WIQL queries return up to **200 work-item IDs per call** (Azure cap). The migrator paginates by chunking IDs client-side at 200. The `WorkItemTrackingHttpClient.GetWorkItemsAsync` call also caps at 200 IDs per request — chunked the same way. DevOps rate-limit responses surface as `VssServiceResponseException` with `HttpStatusCode==429`. The CLI catches this, honours `Retry-After`, and backs off up to 6 attempts (\~64 s of accumulated backoff). Past 6 attempts, exit code is `7`. ## Binary footprint note The DevOps adapter bundles the official Microsoft TFS SDK (`Microsoft.TeamFoundationServer.Client` + `Microsoft.VisualStudio.Services.Client`), adding \~30 MB to the standalone Ekso CLI binary. This is the bundled trade-off — single binary, no plugin install. If binary size becomes a constraint, the v2 roadmap includes a plugin model that makes DevOps separately installable. ## Troubleshooting DevOps-specific issues | Symptom | Fix | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `exit 3 — DevOps PAT invalid` | Mint a new PAT with **Work Items (Read)** + **Identity (Read)** scopes. Check it's not expired. | | `exit 7 — VssServiceResponseException 429` | Wait — DevOps rate limits are tied to TUs (throughput units). Re-run with `--resume`. | | `Service identity filtered` (info, not error) | "Project Collection Build Service" etc. is correctly filtered. No action needed. | | `Iteration hierarchy flattened` | Documented limitation. `MyProj\Sprint 1\Week 1` flattens to `Sprint 1 / Week 1`. | | `Work item type not on destination process` | Item created; type stored in `Meta.devops_workitemtype`. Add the type to your Ekso process if you want first-class handling. | | `Custom. unknown to field-map` | Reference name didn't match any field on this DevOps project. Run the discovery `curl` to verify. | See [Troubleshooting](/cli/migrate/troubleshooting) for the full per-source error table. ## Why migrate to Ekso Azure DevOps centralises work items, repos, builds, and tests; Ekso focuses on the work-item layer with financial intelligence and AI primitives DevOps doesn't model. See [Ekso vs Azure DevOps](https://ekso.app/compare/ekso-vs-azure-devops) for the broader comparison. ## Where to next * **[Command reference](/cli/migrate/command-reference)** — full flag surface. * **[Identity resolution](/cli/migrate/identity-resolution)** — `--user-strategy` deep-dive. * **[Field mapping](/cli/migrate/field-mapping)** — DevOps reference names. * **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and recovery. # Field mapping Source: https://ekso.dev/cli/migrate/field-mapping Map source-platform custom fields to Ekso process fields with a migration.fields.yaml file. Source platforms have custom fields. Ekso has process fields. The migrator bridges the two with a `migration.fields.yaml` file you author by hand. This page is the canonical format reference. Per-source pages link here for examples specific to each source. ## What this solves Every issue tracker has its own custom-field model: * **Jira** uses `customfield_10026` numeric IDs that vary per tenant. * **Linear** has a single first-class `estimate` decimal. * **Azure DevOps** uses `Custom.` reference names. * **Zendesk** has typed `ticket_field` IDs. * **Gemini** has named custom fields. Without a field map, every source custom field falls through to `DataItem.Meta` losslessly — the data is preserved, but not searchable or filterable as a real field. With a field map, each source field maps to an Ekso process field that *is* searchable, filterable, and editable like any other field. ## How auto-create works When you run `apply --field-map migration.fields.yaml`, the Apply layer's `ProcessFieldApplier` runs **before any item writes**: 1. It reads your YAML. 2. For each entry, it queries the destination `DataProcess` (the one you passed to `--process`) for fields with that name. 3. If the field exists, it's a no-op. 4. If the field doesn't exist, it calls `POST /api/field` to create it on the process. The field's `kind` (text / decimal / picker / toggle) and any picker values come from the YAML. 5. If field creation fails (permission denied, validation error), `apply` exits with code `6` *before* writing any items. This means after `apply` succeeds, your destination process has every field your YAML promised, populated with the source-platform values. You can audit and remove fields post-migration via the admin UI. ## YAML format ```yaml theme={null} : : { ekso: , kind: , ...kind-specific } ``` Top-level keys are source names: `jira`, `linear`, `devops`, `zendesk`, `gemini`. The migrator reads only the section matching the source you're migrating. ## Process mapping (`_process`) Most sources use a Type / Issue Type / Work Item Type to distinguish Bugs from Stories from Tasks. Ekso's equivalent is the [Process](/data-model/data-process). The optional `_process` key under each source maps inbound type names to Ekso process slugs: ```yaml theme={null} jira: _process: Bug: Defect Story: Task Task: Task Epic: Enhancement Sub-task: Task devops: _process: Bug: Defect Task: Task User Story: New Feature Issue: Task ``` When `apply` walks the cache, each item's source type is looked up in `_process` to pick the destination process. If a type isn't listed (or the source has no type concept at all, like Linear), the item falls back to the default process passed on the command line as `--process`. **Pre-flight validation.** Before any items are written, `apply` verifies that **every** process slug it could need actually exists on the destination Ekso tenant — both the `--process` default and every value under any `_process` block. If any one is missing, `apply` aborts at the start with exit code `6` (validation). Half-applied migrations against a half-complete process catalogue are worse than no migration. ### Field kinds | `kind` | Stores | Example source fields | | --------- | --------------------------- | ----------------------------------------- | | `text` | Free-form string | Jira sprint name, Linear project label | | `decimal` | Numeric value | Story points, estimates, custom durations | | `picker` | One value from a fixed list | Severity, Risk, Phase | | `toggle` | True / false | "Has-PR-attached", "Customer-facing" | ### `text` example ```yaml theme={null} jira: customfield_10018: { ekso: Sprint, kind: text } ``` The Apply layer creates a text field named `Sprint` on the destination process if it doesn't exist, then writes the source value into it on every item. ### `decimal` example ```yaml theme={null} jira: customfield_10026: { ekso: StoryPoints, kind: decimal } linear: estimate: { ekso: StoryPoints, kind: decimal } devops: Microsoft.VSTS.Scheduling.StoryPoints: { ekso: StoryPoints, kind: decimal } ``` All three sources can map their estimate field to the same Ekso `StoryPoints` field — handy when you're consolidating multiple sources into one Ekso tenant. ### `picker` example Picker fields are the most expressive. Map source values to Ekso picker values explicitly: ```yaml theme={null} jira: customfield_10031: ekso: Severity kind: picker picker: Critical: P0 High: P1 Medium: P2 Low: P3 ``` The Apply layer creates a `Severity` picker field on the process with the four destination values (`P0`, `P1`, `P2`, `P3`), then maps each Jira severity to its Ekso counterpart on every item. Source values not in the `picker` mapping fall back to `DataItem.Meta`. ### `toggle` example ```yaml theme={null} zendesk: has_attachments: { ekso: HasAttachments, kind: toggle } ``` Zendesk's `has_attachments` boolean becomes an Ekso toggle field. Source values of `true` / `1` / `"yes"` / `"true"` (case-insensitive) become Ekso `true`; everything else becomes `false`. ## Worked examples per source ### Jira ```yaml theme={null} jira: customfield_10026: { ekso: StoryPoints, kind: decimal } customfield_10018: { ekso: Sprint, kind: text } customfield_10031: ekso: Severity kind: picker picker: Critical: P0 High: P1 Medium: P2 Low: P3 ``` **Discovering Jira custom-field IDs.** Jira's per-project custom field IDs are visible in the URL of the field's edit page in admin (`customfield_10026`), or via the REST API: Cloud authenticates with your email and API token against `/rest/api/3`; Data Center / Server authenticates with a Personal Access Token sent as a bearer header against `/rest/api/2`. ```bash theme={null} # Cloud (email + API token): curl -u you@acme.com:ATATT3... \ https://acme.atlassian.net/rest/api/3/field \ | jq '.[] | select(.custom == true) | {id, name}' # Data Center / Server (Personal Access Token): curl -H "Authorization: Bearer " \ https://jira.mycompany.com/rest/api/2/field \ | jq '.[] | select(.custom == true) | {id, name}' ``` Multi-line commands are easier in Windows PowerShell ISE, and you don't need to install `jq`. ```powershell theme={null} # Cloud (email + API token): $cred = [Convert]::ToBase64String( [Text.Encoding]::ASCII.GetBytes(":")) $fields = Invoke-RestMethod ` -Uri "https://.atlassian.net/rest/api/3/field" ` -Headers @{Authorization = "Basic $cred"} $fields | Where-Object { $_.custom -eq $true } | Select-Object id, name | Format-Table # Data Center / Server (Personal Access Token): $fields = Invoke-RestMethod ` -Uri "https://jira.mycompany.com/rest/api/2/field" ` -Headers @{Authorization = "Bearer "} $fields | Where-Object { $_.custom -eq $true } | Select-Object id, name | Format-Table ``` ### Linear ```yaml theme={null} linear: estimate: { ekso: Estimate, kind: decimal } ``` Linear's first-class `estimate` field is the only thing most teams need to map. Custom fields beyond that are uncommon on Linear. ### Azure DevOps ```yaml theme={null} devops: Microsoft.VSTS.Scheduling.StoryPoints: { ekso: StoryPoints, kind: decimal } Microsoft.VSTS.Common.Severity: ekso: Severity kind: picker picker: "1 - Critical": P0 "2 - High": P1 "3 - Medium": P2 "4 - Low": P3 Custom.RootCause: { ekso: RootCause, kind: text } ``` **Discovering DevOps reference names.** From the work-item form: open any work item, click the field, the reference name shows in the field properties. Or via REST: ```bash theme={null} curl -u :PAT https://dev.azure.com/your-org/your-project/_apis/wit/fields?api-version=7.1 \ | jq '.value[] | {referenceName, name, type}' ``` ### Zendesk ```yaml theme={null} zendesk: "360001234567": { ekso: AffectedComponent, kind: text } "360001234568": ekso: CustomerTier kind: picker picker: Free: tier-free Pro: tier-pro Ent: tier-ent ``` Zendesk ticket-field IDs are numeric strings — quote them in YAML so they're treated as keys, not numbers. **Discovering Zendesk ticket field IDs:** ```bash theme={null} curl -u you@acme.com/token:API_TOKEN \ https://acme.zendesk.com/api/v2/ticket_fields.json \ | jq '.ticket_fields[] | {id, title, type}' ``` ### Gemini ```yaml theme={null} gemini: PullRequestUrl: { ekso: PullRequestUrl, kind: text } Severity: ekso: Severity kind: picker picker: Critical: P0 High: P1 Medium: P2 Low: P3 ``` Gemini custom fields are accessed by name. SQL mode reads them from `dbo.IssueCustomFields`; API mode pulls them from the issue payload. ## Without a field map You can run `apply` without `--field-map`. Source custom fields fall through to `DataItem.Meta` losslessly. Each source field becomes a `Meta` key prefixed with the source name (e.g. `Meta.jira_customfield_10026 = "5"`). This is fine for archival migrations where the destination process schema doesn't matter. For active use, write the YAML. ## What gets created on the destination process After `apply` runs with a field map, your destination process has: * A new field for every entry in the YAML (created if it didn't already exist). * Every item populated with values from the source field. The created fields appear under the process's field list in the admin UI. You can rename, reorder, or delete them after the fact — the migration doesn't lock anything. ## Failure modes | Situation | Behaviour | | --------------------------------------------------- | ----------------------------------------------------------------------------------------- | | YAML references a kind the migrator doesn't support | exit `2` (usage error) before any work happens | | `picker` field with no `picker:` value list | exit `6` (validation) — picker fields need values | | Field creation fails (permission denied) | exit `4` (forbidden), no items written | | Source value doesn't match any picker value | the source value is preserved in `Meta`, the item field is left null, and apply continues | | Decimal field gets a non-numeric source value | `Meta` fallback for that item, warn at end | ## Where to next * **[Identity resolution](/cli/migrate/identity-resolution)** — author mapping. * **[Command reference](/cli/migrate/command-reference)** — every flag. * **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes. # Migrate from Countersoft Gemini Source: https://ekso.dev/cli/migrate/gemini Move Countersoft Gemini projects, issues, time entries, attachments, sprints, and versions into Ekso — in two commands. If you're a Countersoft Gemini customer reading this — welcome. Ekso is built by the same team that built Gemini. The migration story is the upgrade story: every concept you rely on in Gemini has a clean home in Ekso, and the migrator was designed with your install in mind. `ekso migrate gemini` pulls projects, issues, comments, attachments, time entries, sprints, versions, and components from Countersoft Gemini into your Ekso tenant. Two commands — `collect` and `apply`. Read [Migrate overview](/cli/migrate/overview) and [Before you start](/cli/migrate/before-you-start) first. ## What gets imported Because the Gemini schema and Ekso's canonical migration schema were designed by the same team, the mapping is the cleanest of any source — most fields transfer 1:1. | Gemini concept | Ekso shape | | --------------------------------------- | -------------------------------------------------------------------------------------------- | | Project | [`DataContainer`](/data-model/data-container) (`Code = project.code`, `Name = project.name`) | | Issue | [`DataItem`](/data-model/data-item) | | Comment | [`DataAnnotation`](/data-model/data-annotation) | | Attachment | [`DataFile`](/data-model/data-file) (multipart upload) | | **Time entry** | [`DataItem.Time[]`](/data-model/data-item) (in the same item POST — billable flag preserved) | | Component | [`ConfigLabel`](/data-model/config-label) (prefix `component:`) + item `Tags` | | Version (release / fix) | [`DataCycle`](/data-model/data-cycle) | | Sprint | [`DataCycle`](/data-model/data-cycle) | | Custom field | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) or `Meta` | | Resource (watcher) | `DataItem.Meta.gemini_resources[]` | | Follower (subscriber) | preserved in `Meta` | | Link (parent/child, related, duplicate) | [`DataLink`](/data-model/data-link) | | User / Group | [`DataUser`](/data-model/data-user) | Authoring fidelity preserved on every comment, every time entry, every issue. ## What does NOT get imported * **Workflow definitions / state machines.** v1 imports the *current* status of each issue. State-transition rules don't migrate — Ekso's process system is a successor; redesign the workflow at migration time. * **License data.** Out of scope for the data migration; that's a sales conversation. * **Saved reports / queries.** Re-author on the Ekso side. ## Before you start Pre-flight checklist on top of [the general one](/cli/migrate/before-you-start): * A running Gemini install (v7+ recommended). * One of: * **SQL access** to the Gemini DB (recommended — fastest, most complete), or * **API access** to the Gemini REST API (works behind the firewall). You don't need both — pick whichever your network and policies allow. See "Two modes" below. Add a `gemini` block to your `migration.config.json`: ```json theme={null} { "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." }, "source": "gemini", "gemini": { "connectionMode": "sql", "connectionString": "Server=gemini-db.local;Database=Gemini761;User Id=migrate;Password=...;TrustServerCertificate=True;", "url": "https://gemini.acme.local", "username": "manager", "apiKey": "..." } } ``` If `connectionMode=sql`, `url`/`username`/`apiKey` are ignored. If `connectionMode=api`, `connectionString` is ignored. You can configure both and switch via `--connection-mode` at runtime. Env-var overrides for CI: * `EKSO_MIGRATE_GEMINI_KEY` overrides `gemini.apiKey`. * `EKSO_MIGRATE_GEMINI_DB_PWD` is read by the connection string when interpolated as `${EKSO_MIGRATE_GEMINI_DB_PWD}`. ## Two modes — when to use which | Mode | When | Pros | Cons | | ----------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | **SQL Server direct** (`connectionMode: "sql"`) | The CLI can reach the Gemini DB on TCP 1433. | Fastest. Reads attachment bytes directly from `varbinary` / FILESTREAM. Every column is queryable. | Many Gemini installs don't expose SQL externally. Needs DB read credentials. | | **REST API** (`connectionMode: "api"`) | The CLI can reach the Gemini web app over HTTPS. | Works through firewalls. No DB credentials. Schema-stable. | Slower for large data sets. Some internal fields aren't exposed. | **Recommendation:** SQL mode for migrations done by the Gemini admin on-site; API mode for everyone else. Override the configured mode at runtime: ```bash theme={null} ekso migrate gemini collect --connection-mode api ... ekso migrate gemini apply --connection-mode api ... ``` ## Step 1 — list projects ```bash theme={null} ekso migrate gemini list-projects \ --config migration.config.json \ --url https://ekso.acme.com ``` Sample output: ``` NAME CODE ID USERS Platform PLAT 1 14 Mobile MOB 2 7 ``` If this fails with a SQL connection error, switch to `--connection-mode api`. ## Step 2 — collect ```bash theme={null} ekso migrate gemini collect \ --config migration.config.json \ --project PLAT \ --url https://ekso.acme.com ``` Repeat `--project` for multiple. Sample run: ``` fetching projects... 1 fetched (SQL mode) fetching PLAT issues... 3214/3214 ok fetching comments... 8104/8104 ok fetching time entries... 9421/9421 ok fetching attachments... 481/481 ok (varbinary read direct) saved cache: ~/.ekso/migrate/gemini-2026-04-29T143205.sqlite ``` Useful flags: * `--connection-mode {sql,api}` — override the configured mode. * `--no-attachments` — skip downloads. * `--no-comments` — skip comments. * `--exclude-closed` — skip items in final-state statuses. * `--resume` — pick up where a killed collect left off. SQL mode is fast — a 10K-issue project usually completes in 5–10 minutes including attachments. API mode is slower, especially for attachment-heavy projects. ## Step 3 — dry-run apply ```bash theme={null} ekso migrate gemini apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com \ --dry-run ``` ## Step 4 — apply for real ```bash theme={null} ekso migrate gemini apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com ``` If interrupted, re-run with `--resume`. ## Time tracking — the strongest fidelity point Gemini has first-class time tracking, and Ekso preserves it natively. Every Gemini time entry becomes one row in `DataItem.Time[]` on its parent item — written **in the same `POST /api/item` call** that creates the item. No separate POST, no time-tracking applier. The billable flag is preserved on each entry's `Meta.source_billable`. After migration: * `ekso time list --item ` lists every imported entry. * `ekso time list --user ` lists every entry by user, across all migrated projects. * Standard Ekso reporting (profitability, cost-center, billable hours) works on the imported data the same way it works on natively-created data. This is Gemini's strongest fidelity point — none of the other adapters preserve time history this completely, because none of the other source platforms model it as well. ## Identity resolution for Gemini Gemini users have email — `--user-strategy match-or-create` is reliable. No special handling needed. Read [Identity resolution](/cli/migrate/identity-resolution). ## Custom fields for Gemini Gemini custom fields are accessed by name. Map them to Ekso process fields: ```yaml theme={null} gemini: PullRequestUrl: { ekso: PullRequestUrl, kind: text } Severity: ekso: Severity kind: picker picker: Critical: P0 High: P1 Medium: P2 Low: P3 ``` Pass it to apply: ```bash theme={null} ekso migrate gemini apply \ --config migration.config.json \ --process proc_engineering \ --field-map migration.fields.yaml ``` In SQL mode, custom field values come from `dbo.IssueCustomFields`. In API mode, they come from the issue payload. Both modes route to the same Ekso `Field[]` slot. See [Field mapping](/cli/migrate/field-mapping). ## Sprints, Versions, Components | Gemini | Ekso | Notes | | ----------------------- | ----------------------------------------------------- | -------------------------------------------- | | Sprint | `DataCycle` | Always — no flag. | | Version (release / fix) | `DataCycle` | Sprint-shaped (name + dates + member items). | | Component | `ConfigLabel` (`component:Backend`) + `DataItem.Tags` | Always — no flag. | Cycles attach to a board on the Ekso side. Pass `--board ` if the tenant has more than one board. ## Iron rule — dual-mode dispatch The Gemini adapter holds two repository implementations: `SqlSrvGeminiRepository` (T-SQL queries) and `ApiGeminiRepository` (HTTP client). The mode is fixed at adapter construction — once you pick SQL or API, the same mode is used for the entire run. If you need to switch modes mid-migration (e.g. SQL for collect, API for re-collect on a different machine), run a fresh `collect` in the new mode. The cache files are interchangeable on apply — apply doesn't care which mode produced the cache. The recommended path is SQL mode whenever your network allows it — it's faster and pulls full-fidelity data including attachment bytes directly from the DB. API mode is the firewall-friendly fallback. ## Older Gemini versions The migrator targets Gemini v7+ schemas. Older versions (v5, v6) work in API mode if your install's API supports it. Schema drift is surfaced in `Meta.gemini_unmapped_columns` — the migration completes; data shape just differs slightly. v2 TODO: explicit version-aware schema modules if a customer needs first-class v5/v6 support. ## Multiple Gemini installations on one SQL Server If your SQL Server hosts multiple Gemini DBs, set the right `Database=` in the connection string: ```json theme={null} "connectionString": "Server=gemini-db;Database=Gemini761_Production;..." ``` ## Self-hosted Ekso Ekso runs self-hosted as well as SaaS. If you've been running Gemini on-prem and want to keep that posture, deploy Ekso to the same network and migrate locally — neither the source data nor the Ekso credentials need to leave your infrastructure. ## Troubleshooting Gemini-specific issues | Symptom | Fix | | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `SQL connection refused` | Network can't reach the DB. Switch to `--connection-mode api`, or run from a host inside the corporate network. | | `SQL credentials invalid` | DB user lacks read on Gemini tables. Grant `db_datareader`. | | `API 401` | Wrong / disabled API key. Get a fresh one from Gemini admin. | | `Schema drift — unknown column` | Older or newer Gemini schema than the supported set. Surfaced in `Meta.gemini_unmapped_columns`; migration still completes. | | `Mode mismatch — SQL configured but only API access` | Pass `--connection-mode api` at the command line. | See [Troubleshooting](/cli/migrate/troubleshooting) for the full per-source error table. ## Why move to Ekso Ekso is what the Gemini team would build today — same DNA, modern stack, AI native, financial intelligence built in. Self-host or SaaS. See [Ekso vs Countersoft Gemini](https://ekso.app/compare/ekso-vs-gemini) for the full upgrade story. ## Where to next * **[Command reference](/cli/migrate/command-reference)** — full flag surface. * **[Identity resolution](/cli/migrate/identity-resolution)** — `--user-strategy` deep-dive. * **[Field mapping](/cli/migrate/field-mapping)** — Gemini custom fields. * **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and recovery. # Identity resolution Source: https://ekso.dev/cli/migrate/identity-resolution How ekso migrate maps source-platform users to Ekso users — match-or-create, match-only, migration-bot. External platforms attribute every comment, worklog, and item to a user. Ekso has to record those authors faithfully or the audit trail breaks. This page explains the three strategies, when to use which, and how to clean up afterwards. ## The three strategies Pass one of `match-or-create` / `match-only` / `migration-bot` to `apply --user-strategy`: | Strategy | What it does | Side effects | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `match-or-create` (default) | For each source user, look for an Ekso user with the same email. If one exists, reuse it. If not, mint a new user (random password, **no invite email sent**). | Tenant gains new users. Original authoring fidelity preserved. | | `match-only` | Match by email. Source users with no Ekso match fall back to `--fallback-user`. | No new users created. Authoring on items where no match was found rolls up to the fallback user. | | `migration-bot` | Force every author to `--fallback-user`. | No matching, no creation. All comments and items appear authored by one user. | Pick by deciding which fidelity matters more. ## How matching works The CLI matches by **email address**, case-insensitive. The match check is a single `GET /api/user` against your Ekso tenant per unique source-user-email; the result is cached in the migration's `IdMap` so the same email isn't matched twice. There is no fuzzy matching, no display-name fallback. Two source users with the same email get treated as the same Ekso user (which is usually what you want — the same person used both Jira and DevOps). Two source users with no email get treated separately. The match runs once per unique email at the start of `apply`. If a source user is created on the source platform mid-migration (rare), they show up as "no match" and follow the strategy's fallback path. ## How `match-or-create` mints users When the strategy is `match-or-create` and a source user has no Ekso match, the CLI calls `POST /api/user` with: * `email` = the source user's email * `name` = the source user's display name * `active` = `true` * A random password generated server-side * `inviteOnCreate` = `false` — **no welcome email is sent** Why no email? A 5,000-user migration would otherwise blast 5,000 strangers with "Welcome to Ekso" emails the day before they were planning to be onboarded. The `inviteOnCreate=false` path lets you mint the users now, then run a deliberate communication later. The minted users have a random password they don't know. Send them through the password-reset flow (or your SSO setup) when you're ready for them to actually sign in. ## When to use which **Default to `match-or-create`** unless you have a reason not to. It preserves authorship without spamming users. ```bash theme={null} ekso migrate jira apply --config migration.config.json --process proc_eng # (--user-strategy match-or-create is the default) ``` **Use `match-only` when** you specifically don't want to mint users: * You're piloting the migration on a sandbox tenant and don't want test users polluting your production directory. * Your tenant uses SSO and you provision users centrally — the migrator shouldn't create them. * You only need a subset of authoring fidelity preserved (the matched ones). ```bash theme={null} ekso migrate jira apply \ --config migration.config.json \ --process proc_eng \ --user-strategy match-only \ --fallback-user user_migration_admin ``` Items and comments authored by source users with no Ekso match get attributed to `user_migration_admin`. The original source-user identity is preserved in the item's `Meta` so you can audit later. **Use `migration-bot` when** authoring fidelity doesn't matter: * The source platform's users aren't real people (synthetic test tenant). * You're consolidating archival data and don't care who wrote what. * You want one user to "own" everything for permission simplicity. ```bash theme={null} ekso migrate jira apply \ --config migration.config.json \ --process proc_eng \ --user-strategy migration-bot \ --fallback-user user_migration_bot ``` Every comment, every item, every worklog appears authored by `user_migration_bot`. The original source-user identity is still preserved in `Meta`. ## Source-specific quirks | Source | Behaviour | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Jira** | Atlassian users have email; `match-or-create` is reliable. On Data Center / Server, local (non-SSO) accounts may have no visible email, so some users fall back to create rather than match. | | **Linear** | Linear users always have email; `match-or-create` is reliable. | | **Azure DevOps** | Service identities (`[Project]\Project Collection Build Service`, etc.) are filtered out at collect time — they're not real users and won't appear as authors. | | **Zendesk** | End-users (customers) and agents (staff) are imported. End-users get tagged `migrated-from:zendesk-enduser`; agents get `migrated-from:zendesk-agent`. Anonymous end-users (no email) follow `--user-strategy` rules. | | **Gemini** | All Gemini users have email; `match-or-create` is reliable. | ## Anonymous users If a source user has no email at all (some Zendesk end-users, some legacy DevOps accounts): * `match-or-create`: minted with a placeholder email like `@anon..local`. Filterable later by tag. * `match-only`: rolls up to `--fallback-user`. * `migration-bot`: rolls up to `--fallback-user`. The placeholder email is *not* a real address — these users can't receive password resets without an admin updating their profile first. ## Tagging migrated users Every user the CLI mints (or matches) is stamped with a tag of the form `migrated-from:` so you can filter, audit, or bulk-update them after the fact. Examples: * `migrated-from:jira` — minted from a Jira migration * `migrated-from:linear` * `migrated-from:zendesk-enduser`, `migrated-from:zendesk-agent` * `migrated-from:devops` * `migrated-from:gemini` You can find them later via `ekso user list --tag migrated-from:jira` or directly in the Ekso admin UI. ## Post-migration cleanup After apply succeeds: 1. **Audit the user list.** `ekso user list --tag migrated-from:` shows every user touched by the migration. 2. **Send password resets** to users you actually want to onboard. Skip the rest — they're filed for audit-trail purposes and don't need to sign in. 3. **Remove placeholder-email users** if you don't need them. Their original source identity is preserved in their profile metadata for audit. 4. **Rotate the `--fallback-user` credentials.** If you used `migration-bot`, that account briefly held a lot of authority. Rotate it or disable it. ## What's preserved on items Even when authoring rolls up to a fallback user, the original source identity is preserved on each item, comment, and time entry in `Meta`. Example: ```json theme={null} { "Meta": { "jira_reporter": "alice@old-company.com", "jira_reporter_name": "Alice Liddell", "jira_reporter_account_id": "5b10ac8d82e05b22cc7d4ef5", "original_created_at": "2024-08-21T16:33:21.000+0000" } } ``` This means even a `migration-bot` run preserves enough information to reconstruct authorship later if you change strategies. ## Where to next * **[Command reference](/cli/migrate/command-reference)** — full flag surface. * **[Field mapping](/cli/migrate/field-mapping)** — `migration.fields.yaml` shape. * **[Troubleshooting](/cli/migrate/troubleshooting)** — recovering from a partial apply. # Migrate from Jira Source: https://ekso.dev/cli/migrate/jira Move Atlassian Jira projects, issues, comments, attachments, links, and worklogs into Ekso — in two commands. `ekso migrate jira` pulls projects, issues, comments, worklogs, attachments, and links from Atlassian Jira — Cloud, Data Center, or Server — into your Ekso tenant. Two commands — `collect` and `apply`. Read [Migrate overview](/cli/migrate/overview) and [Before you start](/cli/migrate/before-you-start) first. ## What gets imported | Jira concept | Ekso shape | | ----------------------------------------- | ------------------------------------------------------------------------------------- | | Project | [`DataContainer`](/data-model/data-container) (`Code = ACME`, `Name = ACME Platform`) | | Issue (any type — Bug, Task, Story, Epic) | [`DataItem`](/data-model/data-item) | | Comment | [`DataAnnotation`](/data-model/data-annotation) | | Attachment | [`DataFile`](/data-model/data-file) (multipart upload) | | Worklog | [`DataItem.Time[]`](/data-model/data-item) (in the same item POST) | | Issue link (relates, blocks, subtask, …) | [`DataLink`](/data-model/data-link) | | Sprint (GreenHopper) | [`DataCycle`](/data-model/data-cycle) | | Fix Version | [`DataCycle`](/data-model/data-cycle) | | Component | [`ConfigLabel`](/data-model/config-label) (prefix `component:`) + item `Tags` | | User (assignee, reporter, commenter) | [`DataUser`](/data-model/data-user) (matched or minted) | | Story Points custom field | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) | | Other custom fields | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) or `Meta` | | Affects Version | `DataItem.Meta.jira_affects_versions[]` (lossless) | Authoring fidelity is preserved: every comment, every worklog, every item is attributed to the original Atlassian user (matched by email, or minted via [identity resolution](/cli/migrate/identity-resolution)). ## What does NOT get imported * **Workflow definitions / transition rules.** The migrator snapshots the *current* status of each issue. Build the workflow on the Ekso side. * **Dashboards, filters, JQL queries.** Migrate operationally, then re-author in Ekso. * **Automations** (Jira Automation rules). Re-author as Ekso [rules](/cli/reference/rule). * **Permission schemes / project roles.** Configure access on the Ekso side. * **Brand customisation, themes, project icons.** Atlassian-specific. ## Editions — Cloud vs Data Center / Server The CLI detects your Jira deployment automatically by calling `GET /rest/api/2/serverInfo` on first use — no configuration needed in the common case. | Edition | Detection | Auth | | -------------------------- | ------------------------------------ | ------------------------------- | | Cloud (`cloud`) | `deploymentType: Cloud` | `username` (email) + `apiToken` | | Data Center (`datacenter`) | `deploymentType: Server` (hosted DC) | `personalAccessToken` | | Server (`server`) | `deploymentType: Server` (on-prem) | `personalAccessToken` | To override detection, set `jira.edition` in your config to one of `auto` (default), `cloud`, `datacenter`, or `server`. You can also pass `--edition ` directly on the `list-projects` and `collect` commands. ## Before you start Pre-flight checklist on top of [the general one](/cli/migrate/before-you-start): * An Atlassian account with read access to the projects you want to migrate. * Your Jira URL. * **Cloud:** An [Atlassian API token](https://id.atlassian.com/manage-profile/security/api-tokens) (free). Site URL is `https://acme.atlassian.net`. * **Data Center / Server:** A Personal Access Token (PAT) — create one in your profile under *Security → Personal Access Tokens*. Requires Jira 8.14 or later; attachment download requires Jira 8.17 or later. ### Cloud config ```json theme={null} { "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." }, "source": "jira", "jira": { "url": "https://acme.atlassian.net", "username": "you@acme.com", "apiToken": "ATATT3..." } } ``` Use `EKSO_MIGRATE_JIRA_TOKEN` to pass the API token via environment variable instead. ### Data Center / Server config ```json theme={null} { "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." }, "source": "jira", "jira": { "url": "https://jira.mycompany.com", "edition": "datacenter", "personalAccessToken": "..." } } ``` Use `EKSO_MIGRATE_JIRA_PAT` to pass the PAT via environment variable instead. ### Data Center — comments and descriptions On Data Center / Server, issue descriptions and comments are returned as server-rendered HTML. The migrator imports them as-is; formatting is preserved. ### Data Center — SSO-fronted instances and attachments If your Data Center instance sits behind SSO (SAML/OIDC), attachment downloads require a browser session that the CLI cannot always establish automatically. When a session cannot be obtained for a specific attachment, the download is skipped and logged — the rest of the migration continues. Check the collect output for any `attachment skipped` lines and download those files manually if needed. ## Step 1 — list projects Verify your credentials work and see what Jira shows you: ```bash theme={null} ekso migrate jira list-projects \ --config migration.config.json \ --url https://ekso.acme.com ``` Sample output: ``` NAME KEY ID LEAD ACME Platform ACME 10001 Jane Doe ACME Mobile ACMEM 10002 John Smith ``` If this exits with code `3`, your credentials are wrong — check the API token (Cloud) or PAT (Data Center/Server). Pass `--exclude-closed` to filter projects in archived state. Pass `--edition ` to override auto-detection. ## Step 2 — collect Pull a single project's issues, comments, worklogs, and attachments into a local SQLite cache: ```bash theme={null} ekso migrate jira collect \ --config migration.config.json \ --project ACME \ --url https://ekso.acme.com ``` `--project` takes the Jira project **Key** (e.g. `ACME`), not the project **Name** ("ACME Platform"). The key is shown in the `KEY` column of `list-projects` above. Repeat `--project` for multiple. Sample run: ``` fetching projects... 1 fetched fetching ACME items... 1247/1247 ok fetching comments... 4083/4083 ok fetching worklogs... 891/891 ok fetching attachments... 312/312 ok saved cache: ~/.ekso/migrate/jira-2026-04-29T143205.sqlite ``` Useful flags: * `--no-attachments` — skip binary download. \~10x faster on attachment-heavy projects. * `--no-comments` — skip comments. * `--exclude-closed` — JQL-filter out resolved issues at the source. * `--resume` — pick up where a killed collect left off. * `--edition ` — override the auto-detected edition. Expect roughly 200 issues/minute on a good network. A 10,000-issue project takes \~50 minutes including comments and attachments. ## Step 3 — dry-run apply Before writing anything to your Ekso tenant, walk the cache and see what would happen: ```bash theme={null} ekso migrate jira apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com \ --dry-run ``` Sample output: ``` DRY-RUN — no writes. would create: 1 container, 47 users, 1247 items, 4083 annotations, 312 files, 89 links, 8 cycles field-map check: ok (3 process fields would be auto-created) ``` If `field-map check` fails (e.g. picker without a value list), exit code is `2` and apply won't run for real until you fix the YAML. ## Step 4 — apply for real ```bash theme={null} ekso migrate jira apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com ``` Sample run: ``` applying container ACME... ok applying users (47)... ok (45 created, 2 matched) applying items (1247)... ok (1247 created) applying annotations (4083)... ok applying files (312)... ok applying links (89)... ok applying cycles (8)... ok (5 sprints, 3 fix-versions) done in 7m43s — exit 0 ``` If the run is interrupted, re-run with `--resume`: ```bash theme={null} ekso migrate jira apply \ --config migration.config.json \ --process proc_engineering \ --resume ``` ## Identity resolution for Jira Atlassian users have email — `--user-strategy match-or-create` (the default) is reliable. Source authors are matched against your Ekso tenant; users without a match are minted with no invite email. Read [Identity resolution](/cli/migrate/identity-resolution) for the full mechanics. ## Custom fields for Jira Story Points, Sprint, custom Severity — these are Jira `customfield_*` fields. Map them to Ekso process fields with a `migration.fields.yaml`: ```yaml theme={null} jira: customfield_10026: { ekso: StoryPoints, kind: decimal } customfield_10018: { ekso: Sprint, kind: text } customfield_10031: ekso: Severity kind: picker picker: Critical: P0 High: P1 Medium: P2 Low: P3 ``` Pass it to apply: ```bash theme={null} ekso migrate jira apply \ --config migration.config.json \ --process proc_engineering \ --field-map migration.fields.yaml ``` `ProcessFieldApplier` runs first; any field referenced by the YAML that doesn't exist on `proc_engineering` is auto-created via `POST /api/field` before any item write. See [Field mapping](/cli/migrate/field-mapping) for the full format and how to discover your custom-field IDs. Without `--field-map`, custom fields fall through to `DataItem.Meta` losslessly (e.g. `Meta.jira_customfield_10026`). ## Sprints, Fix Versions, Components Per the migrator's design, **every** Jira concept maps. There are no opt-in flags: | Jira | Ekso | | ------------------------------ | -------------------------------------------------------- | | Sprint (GreenHopper) | `DataCycle` (one cycle per sprint, items linked) | | Fix Version | `DataCycle` (sprint-shaped: name + dates + member items) | | Affects Version | `DataItem.Meta.jira_affects_versions[]` | | Component | `ConfigLabel` (`component:Backend`) + `DataItem.Tags` | | Release (a closed Fix Version) | `DataCycle` | Cycles attach to a board on the Ekso side. Pass `--board ` if the tenant has more than one board; the CLI auto-discovers when there's exactly one. ## Issue type → Process Multiple Jira issue types (Bug, Task, Story, Epic) feed into a single Ekso `DataProcess` per `apply` invocation. The mapping is one-way; the issue type is preserved in `DataItem.Meta.jira_issuetype`. If you need separate processes per issue type, run `apply` multiple times with different `--process` values and JQL-filtered caches. ## Iron rule — Atlassian's date format Jira returns timestamps in the form `2024-08-21T16:33:21.000+0000` — note the `+0000` offset has no colon. The standard .NET `DateTime` parser trips on this. The CLI's `JiraDateTimeNormaliser` handles it correctly, and there's a regression test pinning the behaviour. If you ever see "Date format unparseable" in apply output, the cache was generated by an older CLI version. Re-collect with the latest CLI. ## Troubleshooting Jira-specific issues | Symptom | Fix | | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `exit 3 — Jira credentials invalid` (Cloud) | Regenerate the API token. Check the email matches the token owner. | | `exit 3 — Jira credentials invalid` (DC/Server) | Regenerate the PAT. Confirm Jira version is 8.14 or later. | | `exit 7 — Jira rate-limit exceeded` | Wait a few minutes; Atlassian's rate limits are per-tenant. Re-run with `--resume`. | | Item created with empty body | Atlassian returned no rendered HTML for an ADF description. Original ADF is preserved in `Meta.jira_description_html`. | | `attachment skipped` lines in DC collect output | SSO is fronting the instance and a download session could not be established. Download those attachments manually. | | `Custom field unknown to field-map` | Check the `customfield_*` ID exists on this Jira tenant. | See [Troubleshooting](/cli/migrate/troubleshooting) for the full per-source error table. ## Why migrate to Ekso Once you've moved a Jira project to Ekso, you get financial intelligence (cost / profitability) and AI-native primitives ([MCP](/cli/reference/mcp)) that don't exist on the Jira side. See [Ekso vs Jira](https://ekso.app/compare/ekso-vs-jira) for the broader comparison. ## Where to next * **[Command reference](/cli/migrate/command-reference)** — full flag surface. * **[Identity resolution](/cli/migrate/identity-resolution)** — `--user-strategy` deep-dive. * **[Field mapping](/cli/migrate/field-mapping)** — write your `migration.fields.yaml`. * **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and recovery. # Migrate from Linear Source: https://ekso.dev/cli/migrate/linear Move Linear projects, issues, comments, attachments, cycles, and labels into Ekso — in two commands. `ekso migrate linear` pulls projects, issues, comments, attachments, cycles, and labels from Linear (GraphQL API) into your Ekso tenant. Two commands — `collect` and `apply`. Read [Migrate overview](/cli/migrate/overview) and [Before you start](/cli/migrate/before-you-start) first. ## What gets imported | Linear concept | Ekso shape | | ----------------------------------- | ------------------------------------------------------------------------------------------ | | Project | [`DataContainer`](/data-model/data-container) | | Issue (`ENG-123`) | [`DataItem`](/data-model/data-item) | | Comment | [`DataAnnotation`](/data-model/data-annotation) | | File attachment | [`DataFile`](/data-model/data-file) (multipart upload) | | Subtask, Relates, Blocks, Duplicate | [`DataLink`](/data-model/data-link) | | Cycle | [`DataCycle`](/data-model/data-cycle) | | Label (issue-level) | Pipe-delimited string in a `Label` custom field on the [`DataItem`](/data-model/data-item) | | Cross-team Project | [`ConfigLabel`](/data-model/config-label) (prefix `project:`) | | User | [`DataUser`](/data-model/data-user) (matched or minted — Linear users always have email) | | Estimate (decimal) | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) | Linear stores descriptions and comments as **markdown directly** — no HTML rendering step. The migrator preserves markdown bodies through to Ekso. ## What does NOT get imported * **Workflow states.** Linear's per-team workflow is custom; only the current state of each issue is preserved. * **URL-bookmark "attachments".** Linear lets you attach a URL as if it were a file; these get logged in `Meta.linear_attachments[]` rather than uploaded. * **Triage queues.** Triage is a Linear-specific workflow concept; the items themselves migrate but the triage state doesn't. * **Customer requests.** Linear's customer-feedback feature isn't modelled in Ekso v1. * **Roadmap initiatives.** Out of scope for v1. ## Before you start Pre-flight checklist on top of [the general one](/cli/migrate/before-you-start): * A Linear workspace where you have admin or read-everything access. * A [Linear personal API key](https://linear.app/settings/api) (free tier supports it). Add a `linear` block to your `migration.config.json`: ```json theme={null} { "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." }, "source": "linear", "linear": { "apiKey": "lin_api_..." } } ``` Or use the env-var override `EKSO_MIGRATE_LINEAR_KEY` for CI use cases. ## Step 1 — list projects Verify your credentials work and see what Linear shows you: ```bash theme={null} ekso migrate linear list-projects \ --config migration.config.json \ --url https://ekso.acme.com ``` Sample output: ``` NAME KEY ID ISSUES Dotcom Rebuild ENG/dotcom 01a3b9c8-1f44-4eaa-9c6e-7d5b2c1d3f4e 312 Helpdesk OPS/helpdesk 02b4ca99-2055-5fbb-ad7f-8e6c3d2e4a5f 128 ``` The `KEY` column is what you pass to `--project` on `collect`. ## Step 2 — collect Pull a single project's issues, comments, and attachments into a local SQLite cache: ```bash theme={null} ekso migrate linear collect \ --config migration.config.json \ --project ENG/dotcom \ --url https://ekso.acme.com ``` Repeat `--project` for multiple. Sample run: ``` fetching projects... 1 fetched fetching dotcom issues... 312/312 ok fetching comments... 842/842 ok fetching attachments... 47/47 ok saved cache: ~/.ekso/migrate/linear-2026-04-29T143205.sqlite ``` Useful flags: * `--no-attachments` — skip downloads. * `--no-comments` — skip comments. * `--resume` — pick up where a killed collect left off. ## Step 3 — dry-run apply ```bash theme={null} ekso migrate linear apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com \ --dry-run ``` Sample output: ``` DRY-RUN — no writes. would create: 1 container, 28 users, 843 items, 2204 annotations, 156 files, 47 links, 6 cycles field-map check: ok (1 process field would be auto-created — Estimate) ``` ## Step 4 — apply for real ```bash theme={null} ekso migrate linear apply \ --config migration.config.json \ --process proc_engineering \ --url https://ekso.acme.com ``` If the run is interrupted, re-run with `--resume`: ```bash theme={null} ekso migrate linear apply \ --config migration.config.json \ --process proc_engineering \ --resume ``` ## Identity resolution for Linear Linear users always have an email — the default `--user-strategy match-or-create` is reliable here. No special handling needed. Read [Identity resolution](/cli/migrate/identity-resolution). ## Custom fields for Linear Linear's main custom field is the first-class `estimate` (a decimal). Most teams need only this one mapping: ```yaml theme={null} linear: estimate: { ekso: Estimate, kind: decimal } ``` Pass it to apply: ```bash theme={null} ekso migrate linear apply \ --config migration.config.json \ --process proc_engineering \ --field-map migration.fields.yaml ``` If your destination process doesn't yet have an `Estimate` field, the Apply layer auto-creates it before any item write. See [Field mapping](/cli/migrate/field-mapping). Without `--field-map`, the estimate falls through to `DataItem.Meta.linear_estimate` losslessly. ## Cycles, Labels, Cross-team Projects | Linear | Ekso | | ------------------ | -------------------------------------------------------------- | | Cycle | `DataCycle` (always — no flag) | | Issue-level label | Pipe-delimited string on the `DataItem`'s `Label` custom field | | Cross-team project | `ConfigLabel` (prefix `project:`) | Linear labels are imported at the issue level only — workspace-wide and team-wide label *definitions* don't migrate as standalone records. Each issue's label set is concatenated into a pipe-delimited string and written to a custom field named `Label` on the resulting `DataItem` (e.g. `Bug|Customer-impact|Needs-design`). The `Label` field is auto-created on the destination process if missing. Linear "projects" that span multiple teams stay as `project:`-prefixed `ConfigLabel`s — single-team projects become containers (per the table above), so the cross-team variety is the only one that needs the label fallback. Cycles attach to a board on the Ekso side. Pass `--board ` if the tenant has more than one board. ## Markdown bodies Linear stores issue descriptions and comments as markdown natively. The migrator passes the markdown through unchanged — Ekso renders it the same way Linear did. You shouldn't see any visual drift on body content. ## Iron rule — cursor pagination terminator Linear's GraphQL API paginates with cursors. The pagination loop terminates only when the response returns **both** `HasNextPage=false` *and* a non-null `EndCursor`. An earlier version of the upstream adapter terminated only on `HasNextPage`, which produced infinite loops on certain edge-case responses (Linear sometimes returns `HasNextPage=true` with a null `EndCursor`). The current CLI handles this correctly with a regression test. If you ever see `Inconsistent pagination response from Linear`, the CLI is detecting the edge case and exiting cleanly rather than looping. Re-run `collect` to retry. ## Troubleshooting Linear-specific issues | Symptom | Fix | | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `exit 3 — Linear API key invalid` | Mint a new key at linear.app → Settings → API → Personal API keys. | | `URL-bookmark attachment skipped` | Logged in `Meta.linear_attachments[]`. Documented limitation — Linear "attachments" can be URLs rather than files. | | `Inconsistent pagination response from Linear` | The pagination terminator detected an edge-case response. Re-run `collect`. | | `exit 7 — Linear rate-limit exceeded` | Linear's GraphQL complexity budget got exhausted. Wait, then re-run with `--resume`. | See [Troubleshooting](/cli/migrate/troubleshooting) for the full per-source error table. ## Why migrate to Ekso Linear is excellent at issue tracking. Ekso adds financial intelligence, multi-tenant SaaS or self-host, and AI-native primitives that Linear doesn't model. See [Ekso vs Linear](https://ekso.app/compare/ekso-vs-linear) for the broader comparison. ## Where to next * **[Command reference](/cli/migrate/command-reference)** — full flag surface. * **[Identity resolution](/cli/migrate/identity-resolution)** — `--user-strategy` deep-dive. * **[Field mapping](/cli/migrate/field-mapping)** — write your `migration.fields.yaml`. * **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and recovery. # Migrate Source: https://ekso.dev/cli/migrate/overview Move projects, issues, comments, attachments, and time history from Jira, Linear, Azure DevOps, Zendesk, or Countersoft Gemini into Ekso — in two commands. ## What `ekso migrate` is `ekso migrate` is a built-in importer that moves projects, work items, comments, attachments, links, and time history from another platform into your Ekso tenant. One source picker, one set of commands, one cache format. It currently supports five sources — and for Jira, all three editions (Cloud, Data Center, and Server) are supported and auto-detected: | Source | Branch | Read | | ------------------ | ---------------------- | ------------------------------------------------ | | Atlassian Jira | `ekso migrate jira` | [Migrate from Jira](/cli/migrate/jira) | | Linear | `ekso migrate linear` | [Migrate from Linear](/cli/migrate/linear) | | Azure DevOps | `ekso migrate devops` | [Migrate from Azure DevOps](/cli/migrate/devops) | | Zendesk | `ekso migrate zendesk` | [Migrate from Zendesk](/cli/migrate/zendesk) | | Countersoft Gemini | `ekso migrate gemini` | [Migrate from Gemini](/cli/migrate/gemini) | Every source uses the same workflow, the same flag surface, and the same destination shape on the Ekso side. Once you've migrated one source, you've migrated them all. ## Two phases — `collect` and `apply` The migrator splits cleanly into two commands: ```bash theme={null} ekso migrate collect ... # source platform → local SQLite cache ekso migrate apply ... # local SQLite cache → Ekso REST ``` Splitting the work is deliberate: * A 50,000-issue Jira collect can take an hour. A network blip during apply must not force re-fetching from the source. * `apply --dry-run` walks the cache and tells you exactly what would be created — no Ekso writes — before you commit. * `apply` is **resumable**. Every successful create writes the source-id → Ekso-id pair into a local `IdMap` table; a re-run skips already-applied rows. * The two phases can run on different machines. Collect on a workstation that can reach the source platform; copy the SQLite file; apply from a CI runner with Ekso credentials. This is the on-prem migration story. The cache lives at `~/.ekso/migrate/-.sqlite` by default. Override with `--working-dir`. ## What you get on the Ekso side The migrator preserves authoring fidelity. Every comment, every worklog, every attachment is attributed to the user who created it on the source platform — not to the running CLI user. | Source concept | Ekso shape | | ------------------------------------ | -------------------------------------------------------------------------------------------- | | Project | [`DataContainer`](/data-model/data-container) | | Issue / Ticket / Work Item | [`DataItem`](/data-model/data-item) | | Comment / Discussion | [`DataAnnotation`](/data-model/data-annotation) | | Attachment | [`DataFile`](/data-model/data-file) (multipart upload) | | Worklog / Time entry | [`DataItem.Time[]`](/data-model/data-item) (in the same item POST) | | Sprint / Cycle / Iteration / Version | [`DataCycle`](/data-model/data-cycle) | | Component / Area / Group / Tag | [`ConfigLabel`](/data-model/config-label) + `DataItem.Tags` | | User / Agent / Identity | [`DataUser`](/data-model/data-user) (matched by email or minted) | | Issue link / Subtask | [`DataLink`](/data-model/data-link) | | Custom field | [`DataItem.Field[]`](/data-model/data-item-field-value) (via field-map) or `Meta` (lossless) | The mapping is the same across sources. Differences are in the source-specific quirks (Jira's date format, Linear's cursor pagination, Zendesk's first-comment-as-description, DevOps' WIQL chunking, Gemini's dual-mode dispatch) — see the per-source pages. Every imported `DataItem` carries a custom field called `ImportSource` that retains the original source key (e.g. `ENG-123` for Linear, `ACME-42` for Jira). The field is auto-created on the destination process if missing. Hide it on the process if it's noise — the value stays on every item either way and gives you a back-reference for support, audits, and re-runs. ## Identity resolution — three strategies External platforms attribute every comment, worklog, and item to a user. Ekso has to record those authors faithfully or the audit trail breaks. The CLI offers three strategies via `--user-strategy`: | Strategy | What it does | | --------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `match-or-create` (default) | Match each source user by email; if no Ekso user has that email, mint one (random password, no invite email sent). | | `match-only` | Match by email only. Source users with no Ekso match fall back to `--fallback-user` (or the running CLI user). | | `migration-bot` | Force every author to a single fallback user. Lossy, but simple if author fidelity isn't important. | The default mints users without sending an invite email so production users don't get a flood of "Welcome to Ekso" emails the day before a migration. See [Identity resolution](/cli/migrate/identity-resolution) for the full mechanics and post-migration cleanup. ## What does not migrate Honest list: * **Workflow definitions / state machines.** The migrator snapshots the *current* state of each item. State-transition rules and screen layouts on the source platform don't transfer — the customer's destination process defines the new shape. * **Permissions / role assignments.** Source-platform ACLs don't map. Configure access on Ekso's side after the migration. * **Brand customisations.** Themes, logos, custom domains are platform-specific. * **Source-platform automations** (Jira automations, DevOps build pipelines, Zendesk macros, Gemini rules). Re-author them as Ekso [rules](/cli/reference/rule). * **Reverse migration.** This is one-way — Ekso → source is not supported. * **Ongoing sync.** This is a one-shot migration tool, not an integration. Each per-source page calls out additional source-specific gaps. ## Before you run anything Read [Before you start](/cli/migrate/before-you-start) — prerequisites, secrets policy, token rotation. Then pick your source. ## Where to next * **[Before you start](/cli/migrate/before-you-start)** — prerequisites and the secrets policy. * **[Command reference](/cli/migrate/command-reference)** — `collect`, `apply`, `list-*`, `status`, every flag. * **[Identity resolution](/cli/migrate/identity-resolution)** — match-or-create, match-only, migration-bot. * **[Field mapping](/cli/migrate/field-mapping)** — `migration.fields.yaml` format and worked examples. * **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and per-source error patterns. # Troubleshooting Source: https://ekso.dev/cli/migrate/troubleshooting Exit codes, common errors per source, and how to recover from a partial migration. `ekso migrate` follows the same exit-code convention as the rest of the CLI ([Configuration](/cli/configuration)) plus a few migration-specific patterns. This page covers what each code means in context, common per-source failures, and how to recover. ## Exit codes in context | Code | Meaning during migrate | | ---- | --------------------------------------------------------------------------------------- | | `0` | Success. The cache was written (collect) or every cached row was applied (apply). | | `1` | Generic error — check the message above. | | `2` | Usage error. Bad flags, missing required argument (e.g. `apply` without `--process`). | | `3` | Auth error. Either Ekso credentials or source-platform credentials rejected. | | `4` | Forbidden. The Ekso API key lacks permission to create one of the entity types. | | `5` | Not found. Source project/team/org doesn't exist or the cache file is missing. | | `6` | Validation error. Custom-field auto-create failed, or an item POST got a 422 from Ekso. | | `7` | Rate limited. Either the source platform or Ekso returned 429 too many times. | | `8` | Network error. SQL connection refused, DNS failure, or transient HTTP error past retry. | | `9` | Server error. The source platform or Ekso returned 5xx past retry. | The CLI prints a structured error block on every non-zero exit identifying which row in the cache (and which field, when relevant) caused the failure. ## Recovering from a partial apply Apply is **idempotent** and **resumable**. If something fails halfway: 1. Read the error message — it tells you which row failed and why. 2. Fix the underlying cause (a bad value in the cache, a missing field-map entry, a permissions issue on the destination). 3. Re-run apply with `--resume`. Already-applied rows are skipped via the IdMap; the run picks up at the failed row. ```bash theme={null} ekso migrate jira apply \ --config migration.config.json \ --process proc_engineering \ --resume ``` If the failure is structural (the cache is wrong shape because you upgraded the CLI between collect and apply), re-run `collect` to rebuild the cache, then re-run `apply`. ## Common per-source errors ### Jira | Symptom | Cause | Fix | | ---------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exit 3 — Jira credentials invalid` (Cloud) | Wrong email or expired API token | Regenerate the token at id.atlassian.com → Security → API tokens. Check the email matches the token owner. | | `exit 3 — Jira credentials invalid` (Data Center / Server) | Wrong, expired, or revoked Personal Access Token (PAT) | Regenerate the PAT in Jira under your profile → Personal Access Tokens. Requires Jira 8.14 or later. | | `exit 7 — Jira rate-limit exceeded` | Atlassian returned 429 past the retry budget | Wait a few minutes; Atlassian's rate limits are per-tenant per-IP. Re-run with `--resume`. | | `Item created with empty body` warning | Atlassian returned no rendered HTML for ADF description | Item is created; the original ADF JSON is preserved in `Meta.jira_description_html`. Often harmless. | | `Date format unparseable` | Atlassian returned a date with a no-colon offset (`+0000`) the standard parser rejects | Already handled by `JiraDateTimeNormaliser` — if you see this, the cache is from an older CLI; re-collect. | | Some attachments skipped during a Data Center collect | An SSO-fronted instance blocked the download session, so a file couldn't be fetched | Expected; issues, comments, and worklogs still migrate. Download the skipped files manually — see the Data Center notes in the [Jira guide](/cli/migrate/jira). | | `Custom field unknown to field-map` | YAML references a `customfield_*` ID that doesn't exist on this Jira instance | Check the ID via the discovery `curl` command in [Field mapping](/cli/migrate/field-mapping). | ### Linear | Symptom | Cause | Fix | | ---------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `exit 3 — Linear API key invalid` | Wrong or revoked API key | Mint a new one at linear.app → Settings → API → Personal API keys | | `Inconsistent pagination response from Linear` | Linear returned `HasNextPage=true` with a null `EndCursor` | The CLI's pagination terminator handles this — it logs a safety break and exits cleanly. Re-run `collect` to retry. | | `URL-bookmark attachment skipped` | Linear "attachments" are sometimes URL bookmarks, not files | Logged in `Meta.linear_attachments[]`. No file is uploaded. Documented limitation. | | `Cycle creates without items` warning | A cycle was empty in Linear at collect time | Cycle is created on the Ekso side anyway — populate it later when items move into it. | ### Azure DevOps | Symptom | Cause | Fix | | ------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | | `exit 3 — DevOps PAT invalid` | Personal Access Token expired or wrong scope | Mint a new PAT with **Work Items (Read)** + **Identity (Read)** scopes. | | `exit 7 — VssServiceResponseException 429` | DevOps returned 429 past retry | Wait — DevOps rate limits are tied to throughput units. Re-run with `--resume`. | | `Service identity filtered` info | "Project Collection Build Service" etc. is not a real user | Filtered at collect time. No action needed. | | `Iteration hierarchy flattened` | DevOps iterations like `MyProj\Sprint 1\Week 1` flatten to `Sprint 1 / Week 1` | Documented limitation. Hierarchical cycle support is on the v2 roadmap. | | `Work item type not on destination process` | Source had a Bug type the destination process doesn't model | Item is created; type preserved in `Meta.devops_workitemtype`. | ### Zendesk | Symptom | Cause | Fix | | -------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------- | | `exit 3 — Zendesk credentials invalid` | Wrong email/token combo | Tokens use `email/token:API_TOKEN` basic auth; check both. | | `Synthetic container created` | Zendesk instance has no orgs | Expected behaviour — every ticket lands in a single `zendesk-default` container. | | `Anonymous end-user minted with placeholder email` | Source user has no email | Tagged `migrated-from:zendesk-anon` in Ekso. Use `--user-strategy migration-bot` if you don't want these. | | `Attachment over 20MB skipped` | Ekso file-size limit exceeded | Skipped with warning; file URL preserved in `Meta`. | | `Public/private comment flag` | Zendesk has visibility flags | Preserved as annotation `Tags=["public"]` or `["private"]`. | ### Gemini | Symptom | Cause | Fix | | ---------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `SQL connection refused` | Network can't reach the Gemini DB | Switch to `--connection-mode api`, or run the migration from inside the corporate network where SQL is reachable. | | `SQL credentials invalid` | DB user doesn't have read on Gemini tables | Grant `db_datareader` to the migration user on the Gemini DB. | | `API 401` | Gemini API key wrong / Gemini admin disabled | Get a fresh API key from the Gemini admin surface. | | `Schema drift — unknown column` | Gemini schema differs from the supported version | Surfaced in `Meta.gemini_unmapped_columns`. The migration completes; data shape just differs slightly. | | `Mode mismatch — SQL configured but only API access` | `connectionMode=sql` but the SQL host isn't reachable | Pass `--connection-mode api` to override at the command line. | ## How to read the apply summary After `apply` completes (success or failure), the CLI prints a per-entity summary: ``` applying container ACME... ok applying users (47)... ok (45 created, 2 matched) applying items (1247)... ok (1247 created, 0 skipped) applying annotations (4083)... ok (4071 created, 12 errors) applying files (312)... ok (308 created, 4 skipped) applying links (89)... ok applying cycles (8)... ok done in 7m43s — 12 non-fatal errors, exit 0 ``` The columns you care about: * **created** — new rows written to Ekso. * **matched** — existing Ekso rows reused (users, mostly). * **skipped** — rows the IdMap said were already applied (typical on `--resume`). * **errors** — non-fatal failures (e.g. attachment too big, comment with malformed HTML). Apply continues; exit code reflects whether any errors occurred. If the summary shows non-zero `errors`, scroll up — every error has a structured log entry above the summary identifying the source-id that failed and the specific Ekso response. ## When to re-collect vs re-apply | Situation | Action | | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | Cache file was generated by a different CLI version | Re-collect. Schema-version mismatch refuses to apply. | | Source platform data has changed since collect | Re-collect to refresh. The new collect writes a new timestamped cache; old cache is still around. | | Apply was interrupted mid-flight | Re-apply with `--resume`. Don't re-collect. | | Apply found a bad value in the cache (e.g. malformed date) | Re-collect — fixing the cache by hand is fragile. The new CLI usually has fixes. | | You want a clean tenant after a failed apply | Manually delete the partially-applied entities on the Ekso side, then re-apply. The IdMap keeps the migration idempotent. | ## Getting help * The CLI's `--help` reflects the binary you have installed — treat it as the source of truth if these docs and your CLI ever drift. * Run with `--verbose` to see request/response details. * Open an issue at [github.com/EksoHQ/CLI/issues](https://github.com/EksoHQ/CLI/issues) with the cache file (sanitised) and the verbose log. ## Where to next * **[Command reference](/cli/migrate/command-reference)** — every command and flag. * **[Identity resolution](/cli/migrate/identity-resolution)** — `--user-strategy` deep-dive. * **[Field mapping](/cli/migrate/field-mapping)** — `migration.fields.yaml` format. # Migrate from Zendesk Source: https://ekso.dev/cli/migrate/zendesk Move Zendesk organisations, tickets, comments, and attachments into Ekso — in two commands. `ekso migrate zendesk` pulls organisations, tickets, comments, attachments, and tags from Zendesk Support (REST v2) into your Ekso tenant. Two commands — `collect` and `apply`. Read [Migrate overview](/cli/migrate/overview) and [Before you start](/cli/migrate/before-you-start) first. Zendesk is a support-ticket platform, not an issue tracker. The data shape is different and so is the customer's mental model. This page calls out the differences explicitly. ## What gets imported | Zendesk concept | Ekso shape | | --------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Organisation | [`DataContainer`](/data-model/data-container) (or a synthetic `zendesk-default` container if the instance is flat) | | Ticket | [`DataItem`](/data-model/data-item) | | Comment (public or private) | [`DataAnnotation`](/data-model/data-annotation) (visibility flag preserved as `Tags=["public"]` or `["private"]`) | | Attachment (per-comment) | [`DataFile`](/data-model/data-file) | | End-user (customer) | [`DataUser`](/data-model/data-user) (tagged `migrated-from:zendesk-enduser`) | | Agent (staff) | [`DataUser`](/data-model/data-user) (tagged `migrated-from:zendesk-agent`) | | Group (agent group) | [`ConfigLabel`](/data-model/config-label) (prefix `group:`) + `DataItem.Tags` | | Tag | [`ConfigLabel`](/data-model/config-label) | | Custom ticket field | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) or `Meta` | | Satisfaction rating (CSAT) | `DataItem.Meta.zendesk_csat` | | Brand | `DataItem.Meta.zendesk_brand_id` | ## What does NOT get imported * **SLA policies & breach data.** Read-only fidelity in `Meta.zendesk_sla` only — Ekso v1 has no first-class SLA entity. Use Ekso [clocks](/cli/reference/clock) for SLA replacement going forward. * **Macros & triggers.** Workflow, not data. Re-author as Ekso [rules](/cli/reference/rule). * **Views & filters.** Re-author on the Ekso side. * **Talk / Chat / Sell / Explore** — adjacent products, out of scope. * **Side conversations.** v1 limitation; tracked. * **Article / KB content.** Use Ekso [docs](/cli/reference/docs) and import separately. ## Before you start Pre-flight checklist on top of [the general one](/cli/migrate/before-you-start): * A Zendesk Support instance. * A Zendesk admin account. * A Zendesk API token — generate one in **Admin Centre → Channels → API → Token Access**. * Your Zendesk subdomain — e.g. `acme.zendesk.com`. Add a `zendesk` block to your `migration.config.json`: ```json theme={null} { "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." }, "source": "zendesk", "zendesk": { "url": "https://acme.zendesk.com", "email": "you@acme.com", "apiToken": "abc123..." } } ``` Or use the env-var override `EKSO_MIGRATE_ZENDESK_TOKEN` for CI use cases. Zendesk auth uses the `email/token:API_TOKEN` basic-auth format — the CLI assembles this from `email` and `apiToken`. ## Step 1 — list projects ```bash theme={null} ekso migrate zendesk list-projects \ --config migration.config.json \ --url https://ekso.acme.com ``` The verb is `list-projects` for parity with every other source — for Zendesk this enumerates the **organisations** configured on the workspace, since Zendesk doesn't have a project concept of its own. Each one becomes a `DataContainer` (an Ekso "project") on apply. ``` NAME ID DOMAIN MEMBERS ACME Internal 360...01 acme.com 18 ACME Customers 360...02 external 132 ``` If your Zendesk instance is flat (no organisations configured), the list is empty — that's expected. Every ticket will land in a single synthetic container called `zendesk-default` on apply. ## Step 2 — collect To collect every ticket in the workspace (recommended for first-run): ```bash theme={null} ekso migrate zendesk collect \ --config migration.config.json \ --url https://ekso.acme.com ``` To restrict to a specific organisation (or several — `--project` repeats): ```bash theme={null} ekso migrate zendesk collect \ --config migration.config.json \ --project 360001234567 \ --url https://ekso.acme.com ``` The value is the Zendesk organisation ID (the numeric `360...` shown by `list-projects`). Sample run: ``` fetching orgs... 2 fetched fetching tickets... 8421/8421 ok fetching comments... 26109/26109 ok fetching attachments... 1247/1247 ok saved cache: ~/.ekso/migrate/zendesk-2026-04-29T143205.sqlite ``` Useful flags: * `--no-attachments` — skip downloads. * `--no-comments` — skip comments. (Note: Zendesk has no separate "description" field — the first comment IS the description, so `--no-comments` will leave items with empty bodies.) * `--exclude-closed` — filter out closed tickets at the API layer. * `--resume` — pick up where a killed collect left off. Zendesk's rate limits are aggressive (\~700 requests/minute on Enterprise, 200/minute on lower plans). A 50K-ticket migration on a Pro plan takes \~4 hours and benefits from `--resume`. ## Step 3 — dry-run apply ```bash theme={null} ekso migrate zendesk apply \ --config migration.config.json \ --process proc_support \ --url https://ekso.acme.com \ --dry-run ``` ## Step 4 — apply for real ```bash theme={null} ekso migrate zendesk apply \ --config migration.config.json \ --process proc_support \ --url https://ekso.acme.com ``` Sample run: ``` applying container ACME Internal... ok applying users (162)... ok (40 agents, 122 end-users) applying items (8421)... ok (8421 created) applying annotations (26109)... ok applying files (1247)... ok (1242 created, 5 skipped >20MB) done in 18m12s — exit 0 ``` If interrupted, re-run with `--resume`. ## Identity resolution for Zendesk Zendesk has two identity classes: * **Agents** are tenant-internal staff. They become Ekso `DataUser`s tagged `migrated-from:zendesk-agent`. * **End-users** are customers — the people who opened tickets. They become Ekso `DataUser`s tagged `migrated-from:zendesk-enduser`. Both classes are minted by default with `--user-strategy match-or-create`. The end-user vs agent distinction stays visible via the migrated-from tag so you can later filter, separate by permissions, or remove the end-user accounts entirely if you only need them for audit. **Anonymous end-users** (Zendesk lets customers email in without registering) get a placeholder email like `@anon.zendesk.acme.local` and are tagged `migrated-from:zendesk-anon`. Use `--user-strategy migration-bot --fallback-user user_zendesk_admin` if you'd rather collapse all anonymous end-users into one bot user. Read [Identity resolution](/cli/migrate/identity-resolution) for the full mechanics. ## Custom fields for Zendesk Zendesk ticket field IDs are numeric strings — quote them in YAML: ```yaml theme={null} zendesk: "360001234567": { ekso: AffectedComponent, kind: text } "360001234568": ekso: CustomerTier kind: picker picker: Free: tier-free Pro: tier-pro Ent: tier-ent ``` Discover your ticket field IDs: ```bash theme={null} curl -u you@acme.com/token:API_TOKEN \ https://acme.zendesk.com/api/v2/ticket_fields.json \ | jq '.ticket_fields[] | {id, title, type}' ``` See [Field mapping](/cli/migrate/field-mapping) for the full format. ## Iron rule — first comment as description Zendesk has no separate "description" field on a ticket. The **first comment IS the description**. The migrator handles this: 1. The first comment's body is used as the item's description (`DataItem.Field[CoreFieldType.Description]`). 2. If the ticket has only one comment, no `DataAnnotation` is created — the comment is just the description. 3. If the ticket has multiple comments, all comments (including the first) become `DataAnnotation`s as well, so the audit log is preserved. The Zendesk API natively returns this shape — you don't need any special flags. But if you're comparing the Ekso output against Zendesk's UI, expect items where the description and the first comment look identical. That's expected. ## Public vs private comments Zendesk distinguishes public-facing comments (visible to the requester) from internal notes (visible to agents only). The migrator preserves this: * Public comments → `DataAnnotation` with `Tags=["public"]`. * Internal notes → `DataAnnotation` with `Tags=["private"]`. Filter with `ekso annotation list --item --tag private` after migration. ## Orgs vs flat instances Zendesk has no project concept. The migrator uses **organisations** as the closest analog and maps each org to one Ekso `DataContainer`. If your instance is flat (no orgs configured — common for smaller Zendesk customers), the migrator creates a single synthetic container called `zendesk-default` and lands every ticket there. You can split tickets into multiple Ekso containers post-migration via the admin UI if the flat-container approach doesn't fit. ## Multi-brand Zendesk If your Zendesk instance has multiple brands, the brand ID is preserved in `DataItem.Meta.zendesk_brand_id`. Per-brand container creation is on the v2 roadmap; in v1, the org-as-container model is preserved across brands. ## Troubleshooting Zendesk-specific issues | Symptom | Fix | | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `exit 3 — Zendesk credentials invalid` | The `email/token:API_TOKEN` basic-auth format is unusual — check both fields. Re-issue the token in Admin Centre → API → Token Access. | | `Synthetic container created` | Expected for flat instances. Every ticket lands in `zendesk-default`. | | `Anonymous end-user minted with placeholder email` | Source user has no email. Tagged `migrated-from:zendesk-anon`. | | `Attachment over 20MB skipped` | Ekso file-size limit. Skipped with warning; URL preserved in `Meta`. | | `exit 7 — Zendesk rate-limit exceeded` | Wait — Zendesk limits are tight. Re-run with `--resume`. | See [Troubleshooting](/cli/migrate/troubleshooting) for the full per-source error table. ## Why migrate to Ekso Zendesk specialises in support; Ekso unifies support, project management, and time tracking with financial intelligence. Tickets that turn into work flow naturally without leaving the platform. See [Ekso vs Zendesk](https://ekso.app/compare/ekso-vs-zendesk) for the broader comparison. ## Where to next * **[Command reference](/cli/migrate/command-reference)** — full flag surface. * **[Identity resolution](/cli/migrate/identity-resolution)** — agents vs end-users. * **[Field mapping](/cli/migrate/field-mapping)** — ticket field shapes. * **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and recovery. # Ekso CLI Source: https://ekso.dev/cli/overview Drive Ekso from the command line — automation, scripting, CI integrations, and one-off admin tasks. ## Overview The Ekso CLI is a single binary, `ekso`, that exposes every public Ekso API operation as a curated command. It speaks the same protocol as the [SDK](/sdk/overview) but is shaped for shells, scripts, and CI — not application code. Reach for the CLI when you want to: * Drive Ekso from a terminal during admin work, debugging, or one-off cleanup. * Wire Ekso into shell pipelines (`ekso item list --format json | jq ...`). * Automate from CI/CD without writing a .NET app — issue an API key, set `EKSO_API_KEY`, run `ekso ...`. * Onboard new installs by replaying a folder of curated payloads through `ekso ... --data @file.json`. For programmatic embedding inside a .NET application, prefer the [SDK](/sdk/overview). ## What's in the box The CLI groups operations into 36 top-level **branches** (one per resource — `item`, `board`, `field`, `docs`, `file`, `auth`, …). Each branch carries verbs scoped to that resource (`ekso item list`, `ekso item create`, `ekso field create-date`). Every command shares a global flag surface: `--url`, `--api-key`, `--format`, `--quiet`, `--verbose`, `--no-color`, `--timeout`. Anything you can pass on the command line you can also set via an `EKSO_*` environment variable. See [Configuration](/cli/configuration) for the full list. Output is structured: JSON by default, with table / YAML / JSONL formats available via `--format`. Every command returns a POSIX exit code that maps to a specific failure shape (auth, validation, not-found, rate-limit, etc.) so shell pipelines can branch on the cause. ## Authentication paths Two ways to authenticate: * **`ekso auth login`** — interactive OAuth 2.0 device-flow. Best for humans; tokens auto-refresh and are cached per install URL. * **`--api-key` flag or `EKSO_API_KEY` env var** — non-interactive bearer-token auth. Best for CI, agents, and headless automation. Mint keys via the admin surface. The CLI authenticates as a CLI/SDK client (the JWT carries an `EksoClient=Cli` claim, API keys resolve to `Sdk`), which the backend uses to gate operations that should not be scriptable — for example, system-field updates are rejected from the CLI but allowed from the webapp. See [CLI/SDK marker](/sdk/cli-sdk-marker) for the full semantics. ## Quick taste ```bash theme={null} # Install dotnet tool install -g Ekso.Cli # Authenticate (opens a browser tab to approve the device code) ekso auth login --url https://ekso.acme.com # List your items ekso item list --url https://ekso.acme.com # Create a custom field via the curated typed surface ekso field create-text --name "Customer Reference" --maximum 64 --url https://ekso.acme.com ``` ## Where to next * **[Installation](/cli/installation)** — install the CLI and check the version. * **[Authentication](/cli/authentication)** — device-flow login, API keys, precedence rules. * **[Configuration](/cli/configuration)** — flag/env-var reference, output formats, exit codes. * **[Branches](/cli/branches)** — every command, grouped by resource. * **[Migrate](/cli/migrate/overview)** — switching to Ekso from Jira, Linear, Azure DevOps, Zendesk, or Countersoft Gemini. The CLI talks to your Ekso install by URL — there is no concept of "tenant" in the self-host model. Whatever `PublicUrl` you set during the `/startup` wizard is what you pass via `--url` (or `EKSO_URL`). * **[API Reference](/api-reference/introduction)** — the underlying HTTP surface every CLI command calls. # ekso access-control Source: https://ekso.dev/cli/reference/access-control Access-control entries (permission grants). ## Overview Access-control entries (permission grants). ## Verbs | Verb | Description | | -------- | ---------------------------------------------------------------- | | `list` | List all access-control entries. | | `get` | Fetch a single access-control entry by id. | | `create` | Create a new access-control entry (body via --data). | | `update` | Update an access-control entry (id positional, body via --data). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso access-control --help ekso access-control list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso annotation Source: https://ekso.dev/cli/reference/annotation Append-only annotations attached to items and other entities. ## Overview Append-only annotations attached to items and other entities. ## Verbs | Verb | Description | | ---------------- | -------------------------------------------------------------------------------- | | `list-supported` | List the annotation content types this tenant accepts. | | `create` | Create a new annotation (--owner-id required + --content; or --data @file.json). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso annotation --help ekso annotation list-supported --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso api-key Source: https://ekso.dev/cli/reference/api-key API keys for non-interactive auth (CI / agents / server-to-server). ## Overview API keys for non-interactive auth (CI / agents / server-to-server). ## Verbs | Verb | Description | | -------- | -------------------------------------------------------- | | `list` | List API keys (hashes redacted). | | `create` | Mint a new API key (--name required; rawKey shown once). | | `revoke` | Revoke an API key (soft-delete, idempotent). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso api-key --help ekso api-key list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso area Source: https://ekso.dev/cli/reference/area Sub-organizational areas (departments, teams, sub-orgs). ## Overview Sub-organizational areas (departments, teams, sub-orgs). ## Verbs | Verb | Description | | -------- | ------------------------------------------------ | | `list` | List all areas. | | `get` | Fetch a single area by id. | | `create` | Create an area (body via --data). | | `update` | Update an area (id positional, body via --data). | | `delete` | Delete an area. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso area --help ekso area list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso auth Source: https://ekso.dev/cli/reference/auth Sign in / out and inspect the current session. ## Overview Sign in / out and inspect the current session. ## Verbs | Verb | Description | | -------- | ------------------------------------------------------------------------ | | `login` | Run the device-flow login. Prints a URL + code to enter in your browser. | | `logout` | Clear stored credentials for the current tenant. | | `whoami` | Show who you're signed in as and when the token expires. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso auth --help ekso auth login --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso board Source: https://ekso.dev/cli/reference/board Boards (project / cycle containers). ## Overview Boards (project / cycle containers). ## Verbs | Verb | Description | | ------ | ------------ | | `list` | List boards. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso board --help ekso board list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso budget-reason Source: https://ekso.dev/cli/reference/budget-reason Budget / spend reason codes. ## Overview Budget / spend reason codes. ## Verbs | Verb | Description | | -------- | -------------------------------------------------------- | | `list` | List all budget-reasons. | | `get` | Fetch a single budget-reason by id. | | `create` | Create a budget-reason (body via --data). | | `update` | Update a budget-reason (id positional, body via --data). | | `delete` | Delete a budget-reason. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso budget-reason --help ekso budget-reason list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso clock Source: https://ekso.dev/cli/reference/clock Business clocks (read-only). ## Overview Business clocks (read-only). ## Verbs | Verb | Description | | ------ | --------------------------- | | `list` | List all clocks. | | `get` | Fetch a single clock by id. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso clock --help ekso clock list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso constraint Source: https://ekso.dev/cli/reference/constraint Field validation constraints. ## Overview Field validation constraints. ## Verbs | Verb | Description | | ------------------- | ----------------------------------------------------- | | `list` | List all constraints. | | `get` | Fetch a single constraint by id. | | `list-by-container` | List constraints scoped to a container. | | `create` | Create a constraint (body via --data). | | `update` | Update a constraint (id positional, body via --data). | | `delete` | Delete a constraint. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso constraint --help ekso constraint list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso container Source: https://ekso.dev/cli/reference/container Work containers (projects / queues) and their saved filters. ## Overview Work containers (projects / queues) and their saved filters. ## Verbs | Verb | Description | | --------------- | ------------------------------------------------------ | | `list` | List all containers. | | `get` | Fetch a single container by id. | | `create` | Create a container (body via --data). | | `update` | Update a container (id positional, body via --data). | | `delete` | Delete a container. | | `sequence` | Re-sequence the container's child ordering (no body). | | `filter-list` | List filters scoped to a container. | | `filter-get` | Fetch a single filter (--container required). | | `filter-create` | Create a filter on a container (--container + --data). | | `filter-update` | Update a filter (id positional, --container + --data). | | `filter-delete` | Delete a filter (id positional, --container). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso container --help ekso container list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso content-template Source: https://ekso.dev/cli/reference/content-template Reusable content templates with name, description, and variant array. ## Overview Reusable content templates with name, description, and variant array. ## Verbs | Verb | Description | | -------- | -------------------------------------------------------------------------------------------- | | `list` | List all content templates configured for this tenant. | | `get` | Fetch a single content template by id. | | `create` | Create a new content template (--name + optional flags, or --data @file.json). | | `update` | Update a content template (id positional; flags patch a fetched record, or --data replaces). | | `delete` | Delete a content template. SuperAdmin-only; non-recoverable. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso content-template --help ekso content-template list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso cost-center Source: https://ekso.dev/cli/reference/cost-center Cost centres (finance org). ## Overview Cost centres (finance org). ## Verbs | Verb | Description | | -------- | ------------------------------------------------------ | | `list` | List all cost-centres. | | `get` | Fetch a single cost-centre by id. | | `create` | Create a cost-centre (body via --data). | | `update` | Update a cost-centre (id positional, body via --data). | | `delete` | Delete a cost-centre. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso cost-center --help ekso cost-center list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso crm Source: https://ekso.dev/cli/reference/crm Customer / account records. ## Overview Customer / account records. ## Verbs | Verb | Description | | -------- | ----------------------------------------------------- | | `list` | List all CRM records. | | `get` | Fetch a single CRM record by id. | | `create` | Create a CRM record (body via --data). | | `update` | Update a CRM record (id positional, body via --data). | | `delete` | Delete a CRM record. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso crm --help ekso crm list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso cycle Source: https://ekso.dev/cli/reference/cycle Board cycles (sprints / iterations). ## Overview Board cycles (sprints / iterations). ## Verbs | Verb | Description | | -------- | ------------------------------------------------- | | `list` | List cycles, optionally filtered by --board. | | `get` | Fetch a single cycle by id. | | `create` | Create a new cycle on a board (--board + --data). | | `update` | Update a cycle (id positional, --board + --data). | | `delete` | Delete a cycle (id positional, --board). | | `items` | List items assigned to a cycle. | | `stats` | Item counts grouped by cycle. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso cycle --help ekso cycle list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso docs Source: https://ekso.dev/cli/reference/docs Knowledge-base folders, documents, and doc content. ## Overview Knowledge-base folders, documents, and doc content. ## Verbs | Verb | Description | | ------ | ----------------------------------------------------------------------------- | | `list` | List docs in a folder (--folder required; iterate folders for a global view). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso docs --help ekso docs list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso field Source: https://ekso.dev/cli/reference/field Custom and system field definitions. ## Overview Custom and system field definitions. ## Verbs | Verb | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------ | | `list` | List the full field collection (core + custom, grouped by type). | | `create-date` | Create a date field (--name + optional flags, or --data). | | `create-decimal` | Create a decimal field (--name --minimum --maximum, or --data). | | `create-integer` | Create an integer field (--name --minimum --maximum, or --data). | | `create-percent` | Create a percent field (--name --minimum --maximum, or --data). | | `create-list` | Create a list field (--name --values "a,b,c" --multi-select, or --data). | | `create-picker` | Create a picker field (--name --scope --multi-select, or --data). | | `create-text` | Create a text field (--name --content-type --maximum, or --data). | | `create-time` | Create a time field (--name --allow-day --allow-hour --allow-minute, or --data). | | `create-toggle` | Create a toggle (boolean) field (--name, or --data). | | `update-date` | Update a date field (id positional; flags patch a fetched record, or --data replaces). 403 on system fields. | | `update-decimal` | Update a decimal field. 403 on system fields. | | `update-integer` | Update an integer field. 403 on system fields. | | `update-percent` | Update a percent field. 403 on system fields. | | `update-list` | Update a list field's metadata. 403 on system fields — use update-list-data instead. | | `update-picker` | Update a picker field. 403 on system fields. | | `update-text` | Update a text field. 403 on system fields. | | `update-time` | Update a time field. 403 on system fields. | | `update-toggle` | Update a toggle field. 403 on system fields. | | `update-list-data` | Update a list field's values (the system-field escape hatch — works on Priority etc.). --data with . | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso field --help ekso field list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso file Source: https://ekso.dev/cli/reference/file File attachments on items and annotations. ## Overview File attachments on items and annotations. ## Verbs | Verb | Description | | ---------- | --------------------------------------------------------------------------------------------------- | | `list` | List files on an entity (--item or --annotation, exactly one). | | `get` | Fetch a single file's metadata by id. | | `upload` | Upload a file (positional path; --item or --annotation). Returns the new DataFile. | | `download` | Download a file by id (default: ./\; --output - for stdout; --force overwrites). | | `delete` | Delete a file (DB record + blob; no recovery). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso file --help ekso file list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso hook Source: https://ekso.dev/cli/reference/hook Tenant-wide outbound webhook configuration. ## Overview Tenant-wide outbound webhook configuration. ## Verbs | Verb | Description | | -------- | --------------------------------------------- | | `get` | Get the current webhook config. | | `update` | Replace the webhook config (body via --data). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso hook --help ekso hook get --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso item Source: https://ekso.dev/cli/reference/item Create, read, update, and delete items (tickets / tasks). ## Overview Create, read, update, and delete items (tickets / tasks). ## Verbs | Verb | Description | | -------- | --------------------------------------------------- | | `create` | Create a new item. | | `get` | Fetch a single item by id. | | `list` | List items, optionally filtered by container/board. | | `update` | Update an existing item. | | `delete` | Delete an item. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso item --help ekso item create --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso job-role Source: https://ekso.dev/cli/reference/job-role Job-role definitions. ## Overview Job-role definitions. ## Verbs | Verb | Description | | -------- | --------------------------------------------------- | | `list` | List all job-roles. | | `get` | Fetch a single job-role by id. | | `create` | Create a job-role (body via --data). | | `update` | Update a job-role (id positional, body via --data). | | `delete` | Delete a job-role. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso job-role --help ekso job-role list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso label Source: https://ekso.dev/cli/reference/label Item labels (tags). ## Overview Item labels (tags). ## Verbs | Verb | Description | | -------- | ------------------------------------------------ | | `list` | List all labels. | | `get` | Fetch a single label by id. | | `create` | Create a label (body via --data). | | `update` | Update a label (id positional, body via --data). | | `delete` | Delete a label. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso label --help ekso label list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso link Source: https://ekso.dev/cli/reference/link Entity-to-entity links / relationships. ## Overview Entity-to-entity links / relationships. ## Verbs | Verb | Description | | -------- | --------------------------------------------------------- | | `get` | Get all links originating from an entity (positional id). | | `create` | Create a link (body via --data). | | `delete` | Delete a link. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso link --help ekso link get --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso mail-general Source: https://ekso.dev/cli/reference/mail-general Singleton tenant mail config (signatures, banners, defaults). ## Overview Singleton tenant mail config (signatures, banners, defaults). ## Verbs | Verb | Description | | -------- | -------------------------------------------------- | | `get` | Get the current mail-general config. | | `update` | Replace the mail-general config (body via --data). | | `block` | Update one named block (--block, --content). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso mail-general --help ekso mail-general get --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso mail-mailbox Source: https://ekso.dev/cli/reference/mail-mailbox Mailbox (IMAP/SMTP) configuration. ## Overview Mailbox (IMAP/SMTP) configuration. ## Verbs | Verb | Description | | -------- | ------------------------------------------------------- | | `list` | List all mailboxes. | | `get` | Fetch a single mailbox by id. | | `test` | Run a live connection / auth check against the mailbox. | | `create` | Create a mailbox (body via --data). | | `update` | Update a mailbox (id positional, body via --data). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso mail-mailbox --help ekso mail-mailbox list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso mail-ticketing Source: https://ekso.dev/cli/reference/mail-ticketing Mail-ticketing configuration (incoming-email-to-tickets). ## Overview Mail-ticketing configuration (incoming-email-to-tickets). ## Verbs | Verb | Description | | -------- | ----------------------------------------------------------- | | `list` | List all ticketing configs. | | `get` | Fetch a single ticketing config by id. | | `test` | Run a live connection check against the config. | | `create` | Create a ticketing config (body via --data). | | `update` | Update a ticketing config (id positional, body via --data). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso mail-ticketing --help ekso mail-ticketing list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso mcp Source: https://ekso.dev/cli/reference/mcp Helpers to wire Ekso into Claude Code, Cursor, Windsurf, etc. ## Overview Helpers to wire Ekso into Claude Code, Cursor, Windsurf, etc. ## Verbs | Verb | Description | | --------- | ------------------------------------------------------------------------- | | `connect` | Print the one-liner to add Ekso as an MCP server in your agent of choice. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso mcp --help ekso mcp connect --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso meta Source: https://ekso.dev/cli/reference/meta Reference data lookups (read-only). ## Overview Reference data lookups (read-only). ## Verbs | Verb | Description | | ---------- | -------------------------- | | `currency` | List supported currencies. | | `country` | List supported countries. | | `timezone` | List supported timezones. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso meta --help ekso meta currency --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso non-billable Source: https://ekso.dev/cli/reference/non-billable Non-billable categories (time-entry classification). ## Overview Non-billable categories (time-entry classification). ## Verbs | Verb | Description | | -------- | ---------------------------------------------------------------- | | `list` | List all non-billable categories. | | `get` | Fetch a single non-billable category by id. | | `create` | Create a non-billable category (body via --data). | | `update` | Update a non-billable category (id positional, body via --data). | | `delete` | Delete a non-billable category. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso non-billable --help ekso non-billable list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso notification Source: https://ekso.dev/cli/reference/notification Notification channels and recipient lists. ## Overview Notification channels and recipient lists. ## Verbs | Verb | Description | | -------- | ------------------------------------------------------- | | `list` | List all notifications. | | `get` | Fetch a single notification by id. | | `create` | Create a notification (body via --data). | | `update` | Update a notification (id positional, body via --data). | | `delete` | Delete a notification. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso notification --help ekso notification list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso process Source: https://ekso.dev/cli/reference/process Workflow processes — states, transitions, fields. ## Overview Workflow processes — states, transitions, fields. ## Verbs | Verb | Description | | -------- | -------------------------------------------------- | | `list` | List all processes. | | `get` | Fetch a single process by id. | | `create` | Create a new process (body via --data @file.json). | | `update` | Update a process (id positional, body via --data). | | `delete` | Delete a process. | | `stats` | Item counts grouped by process. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso process --help ekso process list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso reporting Source: https://ekso.dev/cli/reference/reporting Read-only finance / time / risk reports (POST + filter body). ## Overview Read-only finance / time / risk reports (POST + filter body). ## Verbs | Verb | Description | | ------------------------- | ----------------------------- | | `time-code` | Time grouped by code. | | `time-week-summary` | Weekly time summary. | | `user` | Time grouped by user. | | `user-timesheet` | Per-user timesheet. | | `item` | Billable item summary. | | `profitability` | Profitability summary. | | `profitability-drilldown` | Drilldown into profitability. | | `risk-board-cycle` | Risk by board cycle. | | `risk-estimate-logged` | Estimate-vs-logged risk. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso reporting --help ekso reporting time-code --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso rule Source: https://ekso.dev/cli/reference/rule Automation rules — triggers, conditions, actions. ## Overview Automation rules — triggers, conditions, actions. ## Verbs | Verb | Description | | -------- | ----------------------------------------------- | | `list` | List all rules. | | `get` | Fetch a single rule by id. | | `create` | Create a new rule (body via --data @file.json). | | `update` | Update a rule (id positional, body via --data). | | `delete` | Delete a rule. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso rule --help ekso rule list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso screen Source: https://ekso.dev/cli/reference/screen Compute the item screen (form / UI shape) for an item. ## Overview Compute the item screen (form / UI shape) for an item. ## Verbs | Verb | Description | | -------- | ----------------------------------------------- | | `render` | Compute the item screen (--data @request.json). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso screen --help ekso screen render --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso sku Source: https://ekso.dev/cli/reference/sku Revenue / billing SKUs. ## Overview Revenue / billing SKUs. ## Verbs | Verb | Description | | -------- | ---------------------------------------------- | | `list` | List all SKUs. | | `get` | Fetch a single SKU by id. | | `create` | Create a SKU (body via --data). | | `update` | Update a SKU (id positional, body via --data). | | `delete` | Delete a SKU. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso sku --help ekso sku list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso time Source: https://ekso.dev/cli/reference/time Time tracking entries. ## Overview Time tracking entries. ## Verbs | Verb | Description | | --------------- | ------------------------------------------------------------------ | | `create` | Create a time entry (body via --data). | | `update` | Update a time entry (id positional, body via --data). | | `delete` | Delete a time entry. | | `list-for-item` | List time entries on an item (id positional, --data filter). | | `list-by-user` | List items the user logged time on (id positional, --data filter). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso time --help ekso time create --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso user Source: https://ekso.dev/cli/reference/user Tenant users. ## Overview Tenant users. ## Verbs | Verb | Description | | -------- | ------------------------------------------------------------- | | `list` | List all users. | | `get` | Fetch a single user by id. | | `create` | Create a user (body via --data; for bulk import / migration). | | `update` | Update a user (id positional, body via --data). | | `delete` | Delete a user (super admin only). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso user --help ekso user list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso user-group Source: https://ekso.dev/cli/reference/user-group Security groups (membership + role assignments). ## Overview Security groups (membership + role assignments). ## Verbs | Verb | Description | | -------- | ----------------------------------------------------- | | `list` | List all user-groups. | | `get` | Fetch a single user-group by id. | | `create` | Create a user-group (body via --data). | | `update` | Update a user-group (id positional, body via --data). | | `delete` | Delete a user-group. | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso user-group --help ekso user-group list --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ekso watch Source: https://ekso.dev/cli/reference/watch Pins and follow subscriptions on entities. ## Overview Pins and follow subscriptions on entities. ## Verbs | Verb | Description | | ----------------- | ------------------------------------------------ | | `create` | Create a watch (body via --data). | | `update` | Update a watch (id positional, body via --data). | | `delete` | Delete a watch. | | `get` | Fetch a single watch by id. | | `list-by-owner` | List watches on the owner entity. | | `list-by-watcher` | List watches by a user (the watcher). | ## Detailed help Run `ekso` directly to see flags and arguments — `--help` reflects the binary you have installed. ```bash theme={null} ekso watch --help ekso watch create --help ``` ## See also * [CLI overview](/cli/overview) * [Authentication](/cli/authentication) * [Configuration](/cli/configuration) * [API Reference](/api-reference/introduction) — the underlying HTTP routes. # ConfigAccessControl Source: https://ekso.dev/data-model/config-access-control Sample shape for ConfigAccessControl. Sample shape for `ConfigAccessControl`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "accessControlId": "", "area": "example", "role": "", "userGroup": [ "example" ], "isSystem": false } ``` ## Fields | Field | Type | Required | Notes | | ----------------- | ------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `accessControlId` | string | Optional | | | `area` | string | **Required** | | | `role` | [`AppRole`](/data-model/constants#app-role) | **Required** | | | `userGroup` | array\ | Optional | | | `isSystem` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/access-control` * `PUT /api/access-control` ### Response body * `GET /api/access-control/{id}` * `GET /api/access-control` ### CLI `ekso access-control` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigAI Source: https://ekso.dev/data-model/config-ai Sample shape for ConfigAI. Sample shape for `ConfigAI`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "openAIActive": false, "openAIKey": "example", "openAIModel": "example", "azureActive": false, "azureEndpoint": "example", "azureKey": "example", "azureModel": "example", "anthropicActive": false, "anthropicKey": "example", "anthropicModel": "example" } ``` ## Fields | Field | Type | Required | Notes | | ----------------- | ------------------ | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `openAIActive` | boolean | Optional | | | `openAIKey` | string | Optional | | | `openAIModel` | string | Optional | | | `azureActive` | boolean | Optional | | | `azureEndpoint` | string | Optional | | | `azureKey` | string | Optional | | | `azureModel` | string | Optional | | | `anthropicActive` | boolean | Optional | | | `anthropicKey` | string | Optional | | | `anthropicModel` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `PUT /api/tenant/ai` ### Response body * `GET /api/tenant/ai` # ConfigApplicationSetting Source: https://ekso.dev/data-model/config-application-setting Sample shape for ConfigApplicationSetting. Sample shape for `ConfigApplicationSetting`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "value": "example" } ``` ## Fields | Field | Type | Required | Notes | | ----------- | ------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `value` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/admin/setting` * `PUT /api/admin/setting` # ConfigBudgetReason Source: https://ekso.dev/data-model/config-budget-reason Sample shape for ConfigBudgetReason. Sample shape for `ConfigBudgetReason`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "code": "EXAMPLE", "active": false } ``` ## Fields | Field | Type | Required | Notes | | ----------- | ------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `code` | string | **Required** | | | `active` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/finance/reason-code` * `PUT /api/finance/reason-code` ### Response body * `GET /api/finance/reason-code/{id}` * `GET /api/finance/reason-code` ### CLI `ekso budget-reason` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigClock Source: https://ekso.dev/data-model/config-clock Sample shape for ConfigClock. Sample shape for `ConfigClock`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "container": [ "example" ], "timezone": "example", "dayStart": "example", "dayEnd": "example", "weekStart": "example", "weekEnd": "example", "nonWorkingDay": [ "example" ], "tick": "", "greenThreshold": 0, "amberThreshold": 0, "redThreshold": 0 } ``` ## Fields | Field | Type | Required | Notes | | ---------------- | ----------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `container` | array\ | Optional | | | `timezone` | string | **Required** | | | `dayStart` | string | **Required** | | | `dayEnd` | string | **Required** | | | `weekStart` | string | **Required** | | | `weekEnd` | string | **Required** | | | `nonWorkingDay` | array\ | Optional | | | `tick` | [`ClockTick`](/data-model/constants#clock-tick) | **Required** | | | `greenThreshold` | integer | Optional | | | `amberThreshold` | integer | Optional | | | `redThreshold` | integer | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/clock` * `PUT /api/clock/{id}` ### Response body * `GET /api/clock/{id}` * `GET /api/clock` ### CLI `ekso clock` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigCondition Source: https://ekso.dev/data-model/config-condition Sample shape for ConfigCondition. Sample shape for `ConfigCondition`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "leftSide": "example", "rightSide": "example", "operator": "" } ``` ## Fields | Field | Type | Required | Notes | | ----------- | ----------------------------------------------------- | ---------- | --------------------------------------- | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `leftSide` | string | Optional | | | `rightSide` | string | Optional | | | `operator` | [`OperatorType`](/data-model/constants#operator-type) | Optional | | # ConfigConditionGroup Source: https://ekso.dev/data-model/config-condition-group Sample shape for ConfigConditionGroup. Sample shape for `ConfigConditionGroup`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "condition": [ "" ] } ``` ## Fields | Field | Type | Required | Notes | | ----------- | --------------------------------------------------------- | -------- | ----- | | `condition` | array\<[`ConfigCondition`](/data-model/config-condition)> | Optional | | # ConfigContentTemplate Source: https://ekso.dev/data-model/config-content-template Sample shape for ConfigContentTemplate. Sample shape for `ConfigContentTemplate`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "description": "Example description.", "owner": "", "isSystem": false, "variant": [ "" ] } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ------------------------------------------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `description` | string | **Required** | | | `owner` | [`ContentOwner`](/data-model/constants#content-owner) | **Required** | | | `isSystem` | boolean | Optional | | | `variant` | array\<[`ConfigContentTemplateVariant`](/data-model/config-content-template-variant)> | **Required** | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/template/content` * `PUT /api/template/content` ### Response body * `GET /api/template/content/{id}` * `GET /api/template/content` ### CLI `ekso content-template` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigContentTemplateVariant Source: https://ekso.dev/data-model/config-content-template-variant Sample shape for ConfigContentTemplateVariant. Sample shape for `ConfigContentTemplateVariant`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "locale": "example", "text": "example" } ``` ## Fields | Field | Type | Required | Notes | | -------- | ------ | ------------ | ----- | | `locale` | string | **Required** | | | `text` | string | **Required** | | # ConfigCostCenter Source: https://ekso.dev/data-model/config-cost-center Sample shape for ConfigCostCenter. Sample shape for `ConfigCostCenter`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "code": "EXAMPLE", "name": "Example name", "financeCode": "example" } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `code` | string | **Required** | | | `name` | string | **Required** | | | `financeCode` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/finance/cost` * `PUT /api/finance/cost` ### Response body * `GET /api/finance/cost/{id}` * `GET /api/finance/cost` ### CLI `ekso cost-center` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigHook Source: https://ekso.dev/data-model/config-hook Sample shape for ConfigHook. Sample shape for `ConfigHook`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "itemAdd": "example", "itemUpdate": "example", "itemDelete": "example", "userAdd": "example", "userDelete": "example", "containerAdd": "example", "containerDelete": "example", "boardAdd": "example", "boardDelete": "example" } ``` ## Fields | Field | Type | Required | Notes | | ----------------- | ------------------ | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `itemAdd` | string | Optional | | | `itemUpdate` | string | Optional | | | `itemDelete` | string | Optional | | | `userAdd` | string | Optional | | | `userDelete` | string | Optional | | | `containerAdd` | string | Optional | | | `containerDelete` | string | Optional | | | `boardAdd` | string | Optional | | | `boardDelete` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `PUT /api/admin/hook` ### CLI `ekso hook` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigJobRole Source: https://ekso.dev/data-model/config-job-role Sample shape for ConfigJobRole. Sample shape for `ConfigJobRole`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "code": "EXAMPLE", "name": "Example name", "hourlyBaseCost": 0.0, "hourlyChargeRate": 0.0 } ``` ## Fields | Field | Type | Required | Notes | | ------------------ | ------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `code` | string | **Required** | | | `name` | string | **Required** | | | `hourlyBaseCost` | number | **Required** | | | `hourlyChargeRate` | number | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/finance/role` * `PUT /api/finance/role` ### Response body * `GET /api/finance/role/{id}` * `GET /api/finance/role` ### CLI `ekso job-role` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigLabel Source: https://ekso.dev/data-model/config-label Sample shape for ConfigLabel. Sample shape for `ConfigLabel`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "bgColor": "example" } ``` ## Fields | Field | Type | Required | Notes | | ----------- | ------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `bgColor` | string | **Required** | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/label` * `PUT /api/label` ### Response body * `GET /api/label/{id}` * `GET /api/label` ### CLI `ekso label` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigMailGeneral Source: https://ekso.dev/data-model/config-mail-general Sample shape for ConfigMailGeneral. Sample shape for `ConfigMailGeneral`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "signature": "example", "domainStopList": [ "example" ], "subjectStopList": [ "example" ] } ``` ## Fields | Field | Type | Required | Notes | | ----------------- | ------------------ | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `signature` | string | Optional | | | `domainStopList` | array\ | Optional | | | `subjectStopList` | array\ | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `PUT /api/mail/general` ### Response body * `GET /api/mail/general` ### CLI `ekso mail-general` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigMailbox Source: https://ekso.dev/data-model/config-mailbox Sample shape for ConfigMailbox. Sample shape for `ConfigMailbox`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "connectionType": "", "server": "example", "port": 0, "inboxFolder": "example", "categoryProcessed": "example", "categorySkipped": "example", "imapUsername": "example", "imapPassword": "example", "imapSmtpServer": "example", "imapSmtpPort": 0, "microsoftEmailAddress": "user@example.com", "microsoftOAuthTenantId": "", "microsoftOAuthClientId": "", "microsoftOAuthClientSecret": "example" } ``` ## Fields | Field | Type | Required | Notes | | ---------------------------- | ------------------------------------------------------------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `connectionType` | [`MailboxConnectionType`](/data-model/constants#mailbox-connection-type) | Optional | | | `server` | string | Optional | | | `port` | integer | Optional | | | `inboxFolder` | string | Optional | | | `categoryProcessed` | string | Optional | | | `categorySkipped` | string | Optional | | | `imapUsername` | string | Optional | | | `imapPassword` | string | Optional | | | `imapSmtpServer` | string | Optional | | | `imapSmtpPort` | integer | Optional | | | `microsoftEmailAddress` | string | Optional | | | `microsoftOAuthTenantId` | string | Optional | | | `microsoftOAuthClientId` | string | Optional | | | `microsoftOAuthClientSecret` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/mail/mailbox` * `PUT /api/mail/mailbox/{id}` ### Response body * `GET /api/mail/mailbox/{id}` * `GET /api/mail/mailbox` ### CLI `ekso mail-mailbox` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigNonBillable Source: https://ekso.dev/data-model/config-non-billable Sample shape for ConfigNonBillable. Sample shape for `ConfigNonBillable`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "code": "EXAMPLE", "name": "Example name", "description": "Example description.", "group": [ "example" ], "active": false } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `code` | string | **Required** | | | `name` | string | **Required** | | | `description` | string | **Required** | | | `group` | array\ | **Required** | | | `active` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/finance/non-billable` * `PUT /api/finance/non-billable/{id}` ### Response body * `GET /api/finance/non-billable/{id}` * `GET /api/finance/non-billable` ### CLI `ekso non-billable` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigNotification Source: https://ekso.dev/data-model/config-notification Sample shape for ConfigNotification. Sample shape for `ConfigNotification`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "description": "Example description.", "active": false, "container": [ "example" ], "interval": 0, "limiter": 0, "conditionGroup": [ "" ], "recipientUserGroup": [ "example" ], "recipientEmail": [ "user@example.com" ], "recipientEndpoint": "example", "recipientSlackId": "", "recipientMsTeamsId": "" } ``` ## Fields | Field | Type | Required | Notes | | -------------------- | -------------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | Optional | | | `description` | string | Optional | | | `active` | boolean | Optional | | | `container` | array\ | Optional | | | `interval` | integer | Optional | | | `limiter` | integer | Optional | | | `conditionGroup` | array\<[`ConfigConditionGroup`](/data-model/config-condition-group)> | Optional | | | `recipientUserGroup` | array\ | Optional | | | `recipientEmail` | array\ | Optional | | | `recipientEndpoint` | string | Optional | | | `recipientSlackId` | string | Optional | | | `recipientMsTeamsId` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/notification` * `PUT /api/notification` ### Response body * `GET /api/notification/{id}` * `GET /api/notification` ### CLI `ekso notification` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigProcess Source: https://ekso.dev/data-model/config-process Sample shape for ConfigProcess. Sample shape for `ConfigProcess`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "description": "Example description.", "image": "example", "fields": [ "" ], "features": [ "example" ], "workflow": [ "" ] } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ---------------------------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `description` | string | **Required** | | | `image` | string | Optional | | | `fields` | array\<[`ConfigProcessField`](/data-model/config-process-field)> | Optional | | | `features` | array\ | Optional | | | `workflow` | array\<[`ConfigProcessWorkflow`](/data-model/config-process-workflow)> | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/process` * `PUT /api/process/{id}` ### Response body * `GET /api/process/{id}` * `GET /api/process` ### CLI `ekso process` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigProcessField Source: https://ekso.dev/data-model/config-process-field Sample shape for ConfigProcessField. Sample shape for `ConfigProcessField`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldId": "", "order": 0, "required": false } ``` ## Fields | Field | Type | Required | Notes | | ---------- | ------- | -------- | ----- | | `fieldId` | string | Optional | | | `order` | integer | Optional | | | `required` | boolean | Optional | | # ConfigProcessWorkflow Source: https://ekso.dev/data-model/config-process-workflow Sample shape for ConfigProcessWorkflow. Sample shape for `ConfigProcessWorkflow`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldId": "", "value": "example", "next": "example", "userGroup": [ "example" ] } ``` ## Fields | Field | Type | Required | Notes | | ----------- | -------------- | ---------- | --------------------------------------- | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `fieldId` | string | Optional | | | `value` | string | Optional | | | `next` | string | Optional | | | `userGroup` | array\ | Optional | | # ConfigRule Source: https://ekso.dev/data-model/config-rule Sample shape for ConfigRule. Sample shape for `ConfigRule`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "description": "Example description.", "active": false, "owner": "", "ownerId": "", "trigger": "", "triggerEvent": "", "triggerInterval": "example", "container": [ "example" ], "conditionGroup": [ "" ], "actionValue": [ "" ], "actionEndpoint": [ "" ] } ``` ## Fields | Field | Type | Required | Notes | | ----------------- | ----------------------------------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `description` | string | Optional | | | `active` | boolean | Optional | | | `owner` | [`ContentOwner`](/data-model/constants#content-owner) | Optional | | | `ownerId` | string | Optional | | | `trigger` | [`RuleTrigger`](/data-model/constants#rule-trigger) | Optional | | | `triggerEvent` | [`RuleTriggerEvent`](/data-model/constants#rule-trigger-event) | Optional | | | `triggerInterval` | string | Optional | | | `container` | array\ | Optional | | | `conditionGroup` | array\<[`ConfigConditionGroup`](/data-model/config-condition-group)> | Optional | | | `actionValue` | array\<[`ConfigRuleActionValue`](/data-model/config-rule-action-value)> | Optional | | | `actionEndpoint` | array\<[`ConfigRuleActionEndpoint`](/data-model/config-rule-action-endpoint)> | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/rule` * `PUT /api/rule` ### Response body * `GET /api/rule/{id}` * `GET /api/rule` ### CLI `ekso rule` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigRuleActionEndpoint Source: https://ekso.dev/data-model/config-rule-action-endpoint Sample shape for ConfigRuleActionEndpoint. Sample shape for `ConfigRuleActionEndpoint`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "actionType": "", "url": "https://example.com/resource", "httpHeader": [ "example" ], "active": false } ``` ## Fields | Field | Type | Required | Notes | | ------------ | ------------------------------------------------- | ---------- | --------------------------------------- | | `actionType` | [`RuleAction`](/data-model/constants#rule-action) | Optional | | | `url` | string | Optional | | | `httpHeader` | array\ | Optional | | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `active` | boolean | Optional | | # ConfigRuleActionValue Source: https://ekso.dev/data-model/config-rule-action-value Sample shape for ConfigRuleActionValue. Sample shape for `ConfigRuleActionValue`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "actionType": "", "field": "example", "value": "example", "active": false } ``` ## Fields | Field | Type | Required | Notes | | ------------ | ------------------------------------------------- | ---------- | --------------------------------------- | | `actionType` | [`RuleAction`](/data-model/constants#rule-action) | Optional | | | `field` | string | Optional | | | `value` | ? | Optional | | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `active` | boolean | Optional | | # ConfigSku Source: https://ekso.dev/data-model/config-sku Sample shape for ConfigSku. Sample shape for `ConfigSku`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "code": "EXAMPLE", "name": "Example name", "unitPrice": 0.0, "financeCode": "example" } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `code` | string | **Required** | | | `name` | string | **Required** | | | `unitPrice` | number | Optional | | | `financeCode` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/finance/sku` * `PUT /api/finance/sku` ### Response body * `GET /api/finance/sku/{id}` * `GET /api/finance/sku` ### CLI `ekso sku` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigTicket Source: https://ekso.dev/data-model/config-ticket Sample shape for ConfigTicket. Sample shape for `ConfigTicket`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "mailboxId": "", "containerId": "", "processId": "", "ackTemplateSubject": "example", "ackTemplateBody": "example", "replyTemplateSubject": "example", "replyTemplateBody": "example", "startFrom": "2026-04-29T14:30:00Z", "startFromUid": 0, "domainList": [ "example" ], "domainListType": "" } ``` ## Fields | Field | Type | Required | Notes | | ---------------------- | ----------------------------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `mailboxId` | string | **Required** | | | `containerId` | string | **Required** | | | `processId` | string | **Required** | | | `ackTemplateSubject` | string | **Required** | | | `ackTemplateBody` | string | **Required** | | | `replyTemplateSubject` | string | **Required** | | | `replyTemplateBody` | string | **Required** | | | `startFrom` | string (date-time) | **Required** | | | `startFromUid` | integer | Optional | | | `domainList` | array\ | Optional | | | `domainListType` | [`TicketDomainListType`](/data-model/constants#ticket-domain-list-type) | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/mail/ticketing` * `PUT /api/mail/ticketing/{id}` ### Response body * `GET /api/mail/ticketing/{id}` * `GET /api/mail/ticketing` ### CLI `ekso mail-ticketing` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # ConfigUserGroup Source: https://ekso.dev/data-model/config-user-group Sample shape for ConfigUserGroup. Sample shape for `ConfigUserGroup`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "description": "Example description.", "type": "", "member": [ "example" ] } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ----------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `description` | string | Optional | | | `type` | [`GroupType`](/data-model/constants#group-type) | Optional | | | `member` | array\ | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/security/user-group` * `PUT /api/security/user-group` ### Response body * `GET /api/security/user-group/{id}` * `GET /api/security/user-group` ### CLI `ekso user-group` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # Constants Source: https://ekso.dev/data-model/constants Every enum and constant value referenced by Ekso's data models, in one place. Every enum referenced by an Ekso data model lives here with its variant names and integer values. When the API exposes a numeric enum value (e.g. `owner: 30`), this is the page that tells you `30 = Item`. API responses surface these as integers; the SDK exposes them as typed enums. CLI commands accept either the integer or the variant name, depending on the surface — consult each command's `--help` for specifics.

AnnotatorType

| Name | Value | Description | | -------- | ----- | ----------------------------------- | | `User` | `1` | Annotator is a user | | `Email` | `2` | Annotator is inbound email (ticket) | | `System` | `3` | Annotator is an API call | **Used by:** [`DataAnnotation`](/data-model/data-annotation).

AppRole

Generic roles for access control. Example: can "View" boards, can "Add" data (to some part of the application, outside of container/process). | Name | Value | Description | | ------------- | ----- | ------------------------- | | `None` | `0` | | | `View` | `500` | | | `Add` | `510` | | | `Change` | `520` | | | `ChangeOwn` | `521` | | | `Delete` | `530` | | | `DeleteOwn` | `531` | | | `Filter` | `540` | | | `Manage` | `550` | | | `Plan` | `560` | | | `FieldView` | `600` | Process Field level roles | | `FieldAdd` | `610` | | | `FieldUpdate` | `620` | | | `Worker` | `300` | Can be assigned work | **Used by:** [`ConfigAccessControl`](/data-model/config-access-control), [`DataRole`](/data-model/data-role).

ClockStatusEnum

| Name | Value | Description | | ------- | ----- | ----------- | | `Green` | `10` | | | `Amber` | `20` | | | `Red` | `30` | | **Used by:** [`DataItem`](/data-model/data-item).

ClockTick

| Name | Value | Description | | ----------------- | ----- | ----------- | | `None` | `0` | | | `WorkingDay` | `1` | | | `TwentyFourSeven` | `247` | | **Used by:** [`ConfigClock`](/data-model/config-clock).

ConstraintTargetType

| Name | Value | Description | | --------- | ----- | ----------------------------------- | | `Include` | `1` | Include values that are allowed | | `Exclude` | `2` | Exclude values that are not allowed | **Used by:** [`DataConstraint`](/data-model/data-constraint).

ContentOwner

ContentOwner is the owner of the content (no shit!) | Name | Value | Description | | ------------ | ----- | ----------- | | `None` | `0` | | | `System` | `99` | | | `Board` | `10` | | | `Container` | `20` | | | `Item` | `30` | | | `Annotation` | `40` | | | `Mailbox` | `50` | | | `Rule` | `80` | | **Used by:** [`ConfigContentTemplate`](/data-model/config-content-template), [`ConfigRule`](/data-model/config-rule), [`DataAnnotation`](/data-model/data-annotation), [`DataArea`](/data-model/data-area), [`DataFile`](/data-model/data-file), [`DataLink`](/data-model/data-link), [`DataWatch`](/data-model/data-watch).

FieldPlacement

| Name | Value | Description | | --------- | ----- | ----------- | | `None` | `0` | | | `Process` | `10` | | | `Board` | `20` | | **Used by:** [`DataFieldDate`](/data-model/data-field-date), [`DataFieldDecimal`](/data-model/data-field-decimal), [`DataFieldInteger`](/data-model/data-field-integer), [`DataFieldList`](/data-model/data-field-list), [`DataFieldPercent`](/data-model/data-field-percent), [`DataFieldPicker`](/data-model/data-field-picker), [`DataFieldText`](/data-model/data-field-text), [`DataFieldTime`](/data-model/data-field-time), [`DataFieldToggle`](/data-model/data-field-toggle).

FieldUsage

| Name | Value | Description | | ----------- | ----- | ----------- | | `System` | `0` | | | `Input` | `10` | Item | | `Filter` | `20` | Item | | `Condition` | `30` | Rule | | `Action` | `40` | Rule | **Used by:** [`DataFieldDate`](/data-model/data-field-date), [`DataFieldDecimal`](/data-model/data-field-decimal), [`DataFieldInteger`](/data-model/data-field-integer), [`DataFieldList`](/data-model/data-field-list), [`DataFieldPercent`](/data-model/data-field-percent), [`DataFieldPicker`](/data-model/data-field-picker), [`DataFieldText`](/data-model/data-field-text), [`DataFieldTime`](/data-model/data-field-time), [`DataFieldToggle`](/data-model/data-field-toggle).

GroupType

| Name | Value | Description | | ---------- | ----- | ----------- | | `Nobody` | `0` | | | `Everyone` | `1` | | | `Custom` | `2` | | **Used by:** [`ConfigUserGroup`](/data-model/config-user-group).

MailboxConnectionType

| Name | Value | Description | | ----------- | ----- | ----------- | | `Imap` | `1` | | | `Microsoft` | `2` | | **Used by:** [`ConfigMailbox`](/data-model/config-mailbox).

OperatorType

| Name | Value | Description | | -------------------- | ----- | ----------- | | `None` | `0` | | | `Equal` | `10` | | | `NotEqual` | `11` | | | `LessThanOrEqual` | `20` | | | `LessThan` | `21` | | | `GreaterThanOrEqual` | `30` | | | `GreaterThan` | `31` | | | `Contains` | `40` | | | `StartsWith` | `41` | | | `EndsWith` | `42` | | **Used by:** [`ConfigCondition`](/data-model/config-condition).

ResourceType

Identifies the type of resource -- (placeholder) Job Role or (actual) User | Name | Value | Description | | --------- | ----- | ----------- | | `Unknown` | `0` | | | `User` | `1` | | | `JobRole` | `2` | | **Used by:** [`DataItem`](/data-model/data-item).

RuleAction

| Name | Value | Description | | ---------- | ----- | ----------- | | `None` | `0` | | | `Value` | `10` | | | `Endpoint` | `20` | | **Used by:** [`ConfigRuleActionEndpoint`](/data-model/config-rule-action-endpoint), [`ConfigRuleActionValue`](/data-model/config-rule-action-value).

RuleTrigger

| Name | Value | Description | | ------- | ----- | ----------- | | `None` | `0` | | | `Event` | `1` | | | `Timer` | `2` | | **Used by:** [`ConfigRule`](/data-model/config-rule).

RuleTriggerEvent

| Name | Value | Description | | --------------- | ----- | ----------- | | `None` | `0` | | | `OnItemAdd` | `10` | | | `OnItemAdded` | `11` | | | `OnItemUpdated` | `12` | | | `OnItemDeleted` | `13` | | **Used by:** [`ConfigRule`](/data-model/config-rule).

TicketDomainListType

| Name | Value | Description | | ------------ | ----- | ---------------------- | | `Allowed` | `1` | Values are allowed | | `Disallowed` | `2` | Values are not allowed | **Used by:** [`ConfigTicket`](/data-model/config-ticket).

WatchType

| Name | Value | Description | | ---------- | ----- | ----------- | | `Pin` | `1` | | | `Follower` | `2` | | **Used by:** [`DataWatch`](/data-model/data-watch).

WatcherType

Who is watching? Can be external Email address, UserId, UserGroupId | Name | Value | Description | | ----------- | ----- | ----------- | | `User` | `1` | | | `Email` | `2` | | | `UserGroup` | `3` | | **Used by:** [`DataWatch`](/data-model/data-watch). # DataAnnotation Source: https://ekso.dev/data-model/data-annotation Append-only annotation attached to an item, container, or another annotation. Append-only annotation attached to an item, container, or another annotation. Annotations are the primitive for comments, audit-log entries, and inbound-email ticket conversations. Once created, annotations are intentionally not directly editable — they're audit-trail-shaped. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "owner": "Item", "ownerId": "item_01HITEM_LOGIN_BUG", "annotator": "Email", "annotatorId": "customer@acme.example.com", "contentType": "Text", "content": "Hi support — our login flow has been timing out for the last 30 minutes. We're seeing 500 errors after about 30 seconds of inactivity.", "restrictedTo": [ "group_01HGROUP_EVERYONE" ], "meta": [ { "key": "ekso_mailbox_id", "value": "mailbox_01HMAILBOX_SUPPORT" }, { "key": "ekso_ticketing_id", "value": "ticketing_01HTICKETING_HELP" }, { "key": "ekso_sender_email_address", "value": "customer@acme.example.com" }, { "key": "ekso_recipient_email_address", "value": "support@example.com" } ] } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `owner` | [`ContentOwner`](/data-model/constants#content-owner) | **Required** | Type of the entity this annotation hangs off. See [`ContentOwner`](/data-model/constants#content-owner) — usually `Item`; `Annotation` for replies on a thread. | | `ownerId` | string | **Required** | Id of the owning entity. | | `annotator` | [`AnnotatorType`](/data-model/constants#annotator-type) | Optional | Source of the annotation. See [`AnnotatorType`](/data-model/constants#annotator-type). Common values: `User` (in-app comment), `Email` (inbound ticket email), `System` (automation rule). | | `annotatorId` | string | **Required** | Identifier for the annotator. User id when `annotator=User`; sender email address when `annotator=Email`; rule id when `annotator=System`. | | `restrictedTo` | array\ | Optional | Optional ACL — list of user-group ids allowed to read this annotation. Empty array means inherits the owning entity's read permissions. | | `contentType` | string | Optional | Format of `content`. The set of accepted values is tenant-configurable — fetch via `GET /api/annotation/list-supported`. Common: `Text`, `Markdown`, `Html`. | | `content` | string | Optional | The annotation body itself. Plain text, markdown, or HTML depending on `contentType`. | | `meta` | `array<{ key: string, value: string }>` | Optional | Free-form key/value metadata. The ticketing pipeline uses this for `ekso_mailbox_id` / `ekso_ticketing_id` cross-references on inbound emails. | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/annotation` * `POST /api/item/{itemId}/reply` * `PUT /api/annotation/{id}` ### Response body * `GET /api/annotation/owner/{ownerId}` * `GET /api/annotation/{id}` * `POST /api/annotation` ### CLI `ekso annotation` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataArea Source: https://ekso.dev/data-model/data-area Sample shape for DataArea. Sample shape for `DataArea`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "description": "Example description.", "owner": "", "ownerId": "", "archived": false, "parentId": "", "sequence": 0 } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ----------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `description` | string | **Required** | | | `owner` | [`ContentOwner`](/data-model/constants#content-owner) | Optional | | | `ownerId` | string | **Required** | | | `archived` | boolean | Optional | | | `parentId` | string | Optional | | | `sequence` | integer | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/area` * `PUT /api/area` ### Response body * `GET /api/area/{id}` * `GET /api/area` ### CLI `ekso area` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataBoard Source: https://ekso.dev/data-model/data-board Sample shape for DataBoard. Sample shape for `DataBoard`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "description": "Example description.", "container": [ "example" ], "resource": [ "example" ], "budgetHours": 0, "budgetChange": [ "" ], "locked": false, "archived": false, "role": [ "" ] } ``` ## Fields | Field | Type | Required | Notes | | -------------- | -------------------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `description` | string | Optional | | | `container` | array\ | Optional | | | `resource` | array\ | Optional | | | `budgetHours` | integer | Optional | | | `budgetChange` | array\<[`BudgetChangeEntry`](/data-model/budget-change-entry)> | Optional | | | `locked` | boolean | Optional | | | `archived` | boolean | Optional | | | `role` | array\<[`DataRole`](/data-model/data-role)> | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/board` * `PUT /api/board/{id}` ### Response body * `GET /api/board/{id}` * `GET /api/board` ### CLI `ekso board` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataChat Source: https://ekso.dev/data-model/data-chat Sample shape for DataChat. Sample shape for `DataChat`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "userId": "", "title": "Example name", "messages": [ "" ] } ``` ## Fields | Field | Type | Required | Notes | | ----------- | --------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `userId` | string | Optional | | | `title` | string | Optional | | | `messages` | array\<[`ChatEntry`](/data-model/chat-entry)> | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Response body * `GET /api/ai/conversation` # DataConstraint Source: https://ekso.dev/data-model/data-constraint Sample shape for DataConstraint. Sample shape for `DataConstraint`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "container": [ "example" ], "sourceFieldId": "", "sourceFieldDataId": "", "targetFieldId": "", "targetFieldDataId": [ "" ], "targetMode": "" } ``` ## Fields | Field | Type | Required | Notes | | ------------------- | ---------------------------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | | | `container` | array\ | Optional | | | `sourceFieldId` | string | **Required** | | | `sourceFieldDataId` | string | **Required** | | | `targetFieldId` | string | **Required** | | | `targetFieldDataId` | array\ | **Required** | | | `targetMode` | [`ConstraintTargetType`](/data-model/constants#constraint-target-type) | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/constraint` * `PUT /api/constraint/{id}` ### Response body * `GET /api/constraint/container/{containerId}` * `GET /api/constraint/{id}` * `GET /api/constraint` ### CLI `ekso constraint` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataContainer Source: https://ekso.dev/data-model/data-container Top-level work container — a project, queue, product line, or service queue. Top-level work container — a project, queue, product line, or service queue. Every `DataItem` belongs to exactly one container. Containers carry an owner and one or more processes (each defining a workflow + field schema). ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "code": "FEN", "name": "Next-gen App", "description": "Next-gen enterprise automation platform", "label": "label_01HLABEL_PRODUCT", "owner": "user_01HUSER_OWNER", "archived": false, "role": [ { "role": "View", "userGroup": [ "group_01HGROUP_EVERYONE", "group_01HGROUP_DEV" ] }, { "role": "Add", "userGroup": [ "group_01HGROUP_EVERYONE", "group_01HGROUP_DEV" ] }, { "role": "Filter", "userGroup": [ "group_01HGROUP_EVERYONE" ] }, { "role": "Manage", "userGroup": [ "group_01HGROUP_EVERYONE", "group_01HGROUP_DEV" ] }, { "role": "Change", "userGroup": [ "group_01HGROUP_EVERYONE", "group_01HGROUP_DEV" ] }, { "role": "ChangeOwn", "userGroup": [ "group_01HGROUP_EVERYONE", "group_01HGROUP_DEV" ] }, { "role": "Delete", "userGroup": [ "group_01HGROUP_EVERYONE", "group_01HGROUP_DEV" ] }, { "role": "DeleteOwn", "userGroup": [ "group_01HGROUP_EVERYONE", "group_01HGROUP_DEV" ] } ] } ``` ## What Ekso fills in These fields are managed by the server. You don't pass them on writes — they appear on the response. * **`lastSequence`** — Counter for the next item sequence number. Server increments this every time `POST /api/item` creates an item against this container. ## Fields | Field | Type | Required | Notes | | -------------- | ------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `code` | string | **Required** | Short human-readable code. Unique per tenant. Used as a prefix in item identifiers (`FEN-123`). | | `name` | string | **Required** | Display name shown across the UI and reports. | | `description` | string | **Required** | Long-form description. Plain text in the database; some surfaces render markdown. | | `label` | string | Optional | Id of a `ConfigLabel` for visual grouping — colour coded across boards and lists. | | `lastSequence` | integer | Server-set | Server-managed counter. The next item created against this container will be `code-(lastSequence+1)`. | | `owner` | string | Optional | User id of the owning user. The owner has implicit `ChangeOwn` / `DeleteOwn` rights regardless of explicit role grants. | | `archived` | boolean | Optional | When true, the container is hidden from default lists; items remain queryable. | | `role` | array\<[`DataRole`](/data-model/data-role)> | Optional | Per-role access grants. Each entry pairs an `AppRole` with the user-group ids that hold that role on this container. | | `process` | array\<[`DataProcess`](/data-model/data-process)> | Optional | Array of `DataProcess` references attached to this container. The first acts as the default for new items. | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/container` * `PUT /api/container` ### Response body * `GET /api/container/{id}` * `GET /api/container` * `POST /api/container` ### CLI `ekso container` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataCrm Source: https://ekso.dev/data-model/data-crm Sample shape for DataCrm. Sample shape for `DataCrm`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "code": "EXAMPLE", "name": "Example name", "domain": "example", "address": "example", "zipcode": "example", "country": "example", "contacts": [ "" ], "sku": [ "example" ] } ``` ## Fields | Field | Type | Required | Notes | | ----------- | -------------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `code` | string | **Required** | | | `name` | string | **Required** | | | `domain` | string | **Required** | | | `address` | string | **Required** | | | `zipcode` | string | **Required** | | | `country` | string | **Required** | | | `contacts` | array\<[`DataCrmContact`](/data-model/data-crm-contact)> | **Required** | | | `sku` | array\ | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/finance/crm` * `PUT /api/finance/crm` ### Response body * `GET /api/finance/crm/{id}` * `GET /api/finance/crm` ### CLI `ekso crm` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataCrmContact Source: https://ekso.dev/data-model/data-crm-contact Sample shape for DataCrmContact. Sample shape for `DataCrmContact`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "email": "user@example.com", "phone": "example" } ``` ## Fields | Field | Type | Required | Notes | | ------- | ------ | -------- | ----- | | `name` | string | Optional | | | `email` | string | Optional | | | `phone` | string | Optional | | # DataCycle Source: https://ekso.dev/data-model/data-cycle Cycle (sprint, iteration, version, release) — a time-boxed grouping of items. Cycle (sprint, iteration, version, release) — a time-boxed grouping of items. Cycles attach to a board and accumulate items via `cycleId` on each item. Used for planning views, burn-down reports, and per-sprint filtering. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Sprint 42", "description": "Q2 2026 — login & timeout fixes.", "boardId": "board_01HBOARD_FEN", "order": 42, "startDate": "2026-04-15T00:00:00Z", "finishDate": "2026-04-29T23:59:59Z", "budgetHours": 80, "locked": false, "closed": false } ``` ## What Ekso fills in These fields are managed by the server. You don't pass them on writes — they appear on the response. * **`items`** — Maintained automatically — each item carrying `cycleId == ` shows up here. ## Fields | Field | Type | Required | Notes | | ------------- | ------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | Display name of the cycle. Sprint-style names (`Sprint 42`) are common; release-style names (`v2.4.0`) work too. | | `description` | string | **Required** | Brief summary of what's in this cycle. | | `boardId` | string | Optional | Id of the parent board. A cycle belongs to exactly one board. | | `order` | integer | Optional | Ordering hint for cycle lists. Typically the sprint number. | | `parent` | string | Optional | Id of a parent cycle, when cycles nest (rare; some teams use this for release-of-sprints hierarchy). | | `startDate` | string • nullable | Optional | Cycle start. Optional — leave null for ad-hoc / unscheduled cycles. | | `finishDate` | string • nullable | Optional | Cycle end. | | `budgetHours` | integer | Optional | Total time-budget for items in this cycle. Used by burn-down reports. | | `locked` | boolean | Optional | When true, items can't be added or removed from this cycle. | | `closed` | boolean | Optional | When true, the cycle is past — won't appear in active-sprint pickers. | | `items` | array\ | Server-set | Array of item ids assigned to the cycle. Maintained automatically as items' `cycleId` changes. | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/board/{boardId}/cycle` * `PUT /api/board/{boardId}/cycle/{cycleId}` ### Response body * `GET /api/board/cycle` * `GET /api/board/{boardId}/cycle/{cycleId}` * `GET /api/board/{boardId}/cycle` ### CLI `ekso cycle` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataDoc Source: https://ekso.dev/data-model/data-doc Sample shape for DataDoc. Sample shape for `DataDoc`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "folderId": "", "name": "Example name", "context": [ "" ], "restrictedTo": [ "example" ], "aiAccess": false, "archived": false, "author": "example" } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `folderId` | string | Optional | | | `name` | string | **Required** | | | `context` | array\<[`DocContext`](/data-model/doc-context)> | Optional | | | `restrictedTo` | array\ | Optional | | | `aiAccess` | boolean | Optional | | | `archived` | boolean | Optional | | | `author` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/docs/doc` * `PUT /api/docs/doc/{docId}` ### Response body * `GET /api/docs/doc/{docId}` * `GET /api/docs/folder/{folderId}/list` * `POST /api/docs/doc` ### CLI `ekso docs doc` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataDocContent Source: https://ekso.dev/data-model/data-doc-content Sample shape for DataDocContent. Sample shape for `DataDocContent`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "docId": "", "content": "example" } ``` ## Fields | Field | Type | Required | Notes | | ----------- | ------------------ | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `docId` | string | Optional | | | `content` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `PUT /api/docs/doc/{docId}/content` ### Response body * `GET /api/docs/doc/{docId}/content` ### CLI `ekso docs content` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataDocFolder Source: https://ekso.dev/data-model/data-doc-folder Sample shape for DataDocFolder. Sample shape for `DataDocFolder`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "name": "Example name", "color": "example", "restrictedTo": [ "example" ], "archived": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ------------------ | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | Optional | | | `color` | string | Optional | | | `restrictedTo` | array\ | Optional | | | `archived` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/docs/folder` * `PUT /api/docs/folder/{folderId}` ### Response body * `GET /api/docs/folder/{folderId}` * `GET /api/docs/folder` * `POST /api/docs/folder` ### CLI `ekso docs folder` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataFieldDate Source: https://ekso.dev/data-model/data-field-date Sample shape for DataFieldDate. Sample shape for `DataFieldDate`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "minimum": "2026-04-29T14:30:00Z", "maximum": "2026-04-29T14:30:00Z", "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `minimum` | string • nullable | Optional | | | `maximum` | string • nullable | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/date` * `PUT /api/field/date` # DataFieldDecimal Source: https://ekso.dev/data-model/data-field-decimal Sample shape for DataFieldDecimal. Sample shape for `DataFieldDecimal`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "minimum": 0.0, "maximum": 0.0, "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `minimum` | number | Optional | | | `maximum` | number | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/decimal` * `PUT /api/field/decimal` # DataFieldInteger Source: https://ekso.dev/data-model/data-field-integer Sample shape for DataFieldInteger. Sample shape for `DataFieldInteger`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "minimum": 0, "maximum": 0, "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `minimum` | integer | Optional | | | `maximum` | integer | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/integer` * `PUT /api/field/integer` # DataFieldList Source: https://ekso.dev/data-model/data-field-list Sample shape for DataFieldList. Sample shape for `DataFieldList`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "multiSelect": false, "data": [ "" ], "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `multiSelect` | boolean | Optional | | | `data` | array\<[`DataFieldListData`](/data-model/data-field-list-data)> | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/list` * `PUT /api/field/list` # DataFieldListData Source: https://ekso.dev/data-model/data-field-list-data Sample shape for DataFieldListData. Sample shape for `DataFieldListData`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "value": "example", "image": "example", "metadata": "example", "active": false } ``` ## Fields | Field | Type | Required | Notes | | ---------- | ------- | ---------- | --------------------------------------- | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `value` | string | Optional | | | `image` | string | Optional | | | `metadata` | string | Optional | | | `active` | boolean | Optional | | # DataFieldPercent Source: https://ekso.dev/data-model/data-field-percent Sample shape for DataFieldPercent. Sample shape for `DataFieldPercent`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "minimum": 0, "maximum": 0, "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `minimum` | integer | Optional | | | `maximum` | integer | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/percent` * `PUT /api/field/percent` # DataFieldPicker Source: https://ekso.dev/data-model/data-field-picker Sample shape for DataFieldPicker. Sample shape for `DataFieldPicker`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "scope": "example", "multiSelect": false, "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `scope` | string | Optional | | | `multiSelect` | boolean | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/picker` * `PUT /api/field/picker` # DataFieldText Source: https://ekso.dev/data-model/data-field-text Sample shape for DataFieldText. Sample shape for `DataFieldText`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "contentType": "example", "maximum": 0, "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `contentType` | string | Optional | | | `maximum` | integer | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/text` * `PUT /api/field/text` # DataFieldTime Source: https://ekso.dev/data-model/data-field-time Sample shape for DataFieldTime. Sample shape for `DataFieldTime`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "allowDay": false, "allowHour": false, "allowMinute": false, "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `allowDay` | boolean | Optional | | | `allowHour` | boolean | Optional | | | `allowMinute` | boolean | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/time` * `PUT /api/field/time` # DataFieldToggle Source: https://ekso.dev/data-model/data-field-toggle Sample shape for DataFieldToggle. Sample shape for `DataFieldToggle`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldType": "example", "defaultValue": "example", "name": "Example name", "prompt": "example", "description": "Example description.", "isCore": false, "isFeature": false, "featureName": "example", "placement": [ "" ], "usage": [ "" ], "listEditable": false } ``` ## Fields | Field | Type | Required | Notes | | -------------- | ----------------------------------------------------------------- | ---------- | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `fieldType` | string • nullable | Optional | | | `defaultValue` | string | Optional | | | `name` | string | Optional | | | `prompt` | string | Optional | | | `description` | string | Optional | | | `isCore` | boolean | Optional | | | `isFeature` | boolean | Optional | | | `featureName` | string | Optional | | | `placement` | array\<[`FieldPlacement`](/data-model/constants#field-placement)> | Optional | | | `usage` | array\<[`FieldUsage`](/data-model/constants#field-usage)> | Optional | | | `listEditable` | boolean | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/field/toggle` * `PUT /api/field/toggle` # DataFile Source: https://ekso.dev/data-model/data-file File attachment on an item or annotation. File attachment on an item or annotation. Files are uploaded as multipart form-data via the `POST /api/file/upload` endpoint and downloaded as binary streams. The `DataFile` shape is the metadata record returned alongside the uploaded file — the file content itself isn't part of this JSON. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "owner": "Item", "ownerId": "item_01HITEM_LOGIN_BUG", "fileName": "screenshot-of-error.png", "mimeType": "image/png", "sizeBytes": 184320 } ``` ## What Ekso fills in These fields are managed by the server. You don't pass them on writes — they appear on the response. * **`sizeBytes`** — Captured by the upload pipeline once the byte stream finishes streaming to storage. ## Fields | Field | Type | Required | Notes | | ----------- | ----------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `owner` | [`ContentOwner`](/data-model/constants#content-owner) | Optional | Type of the entity this file is attached to. `Item` or `Annotation` only — the controller hard-rejects other owners. | | `ownerId` | string | **Required** | Id of the owning entity. | | `uid` | string | Optional | | | `filename` | string | Optional | | | `mimeType` | string | Optional | MIME type, detected from the upload's content-type header. | | `length` | integer | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Response body * `GET /api/file/{id}` * `GET /api/file/{ownerType}/{ownerId}` ### CLI `ekso file` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataFilter Source: https://ekso.dev/data-model/data-filter Sample shape for DataFilter. Sample shape for `DataFilter`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "containerId": "", "name": "Example name", "active": false, "criteria": "" } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ----------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `containerId` | string | **Required** | | | `name` | string | **Required** | | | `active` | boolean | Optional | | | `criteria` | [`FilterCriteria`](/data-model/filter-criteria) | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/container/{containerId}/filter` * `PUT /api/container/{containerId}/filter/{filterId}` ### Response body * `GET /api/container/{containerId}/filter/{filterId}` * `GET /api/container/{containerId}/filter` # DataItem Source: https://ekso.dev/data-model/data-item Work item — a ticket, task, story, change request, or any unit of work. Work item — a ticket, task, story, change request, or any unit of work. The core primitive of Ekso. Items live inside a container, carry process-defined values in `field`, time history in `time`, and free-form metadata in `meta`. Every human attribute (name, description, status, priority, …) lives in `field` rather than as a top-level property. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "containerId": "container_01HCONTAINER_FEN", "processId": "process_01HPROCESS_DEFAULT", "boardId": "board_01HBOARD_FEN", "cycleId": "cycle_01HCYCLE_SPRINT_42", "resourceType": "User", "resourceId": "user_01HUSER_ASSIGNEE", "dateOpened": "2026-04-15T09:30:00Z", "field": [ { "id": "field_01HFIELD_NAME", "data": "Investigate intermittent timeout in login flow" }, { "id": "field_01HFIELD_DESCRIPTION", "data": "Customers report 500 errors after 30s of inactivity. Repro on Safari 17 + 3G throttling." }, { "id": "field_01HFIELD_STATUS", "data": "fielddata_01HSTATUS_OPEN" }, { "id": "field_01HFIELD_PRIORITY", "data": "fielddata_01HPRIORITY_HIGH" }, { "id": "field_01HFIELD_SEVERITY", "data": "fielddata_01HSEVERITY_MAJOR" }, { "id": "field_01HFIELD_RESOLUTION", "data": "fielddata_01HRESOLUTION_UNRESOLVED" }, { "id": "field_01HFIELD_AREA", "data": "area_01HAREA_BACKEND" }, { "id": "field_01HFIELD_PERCENTCOMPLETE", "data": "25" }, { "id": "field_01HFIELD_WORKESTIMATE", "data": "4h 30m" }, { "id": "field_01HFIELD_OWNEDBY", "data": "user_01HUSER_ASSIGNEE" }, { "id": "field_01HFIELD_SKU", "data": "sku_01HSKU_DEV_HOURS" } ], "time": [ { "userId": "user_01HUSER_ASSIGNEE", "dated": "2026-04-29T14:30:00Z", "timeLogged": "2h 15m", "comment": "Initial triage and reproduction.", "timeTypeId": "timetype_01HTIMETYPE_BILLABLE", "status": "Pending" } ], "meta": [ { "key": "external_url", "value": "https://acme.example.com/tickets/T-1234" } ] } ``` ## What Ekso fills in These fields are managed by the server. You don't pass them on writes — they appear on the response. * **`sequence`** — Auto-incrementing per-container number — the `123` in `FEN-123`. Server assigns it when the item is created. * **`clockStart`** — Set by the clock subsystem when the item enters a clock-eligible state. * **`clockFinish`** — Set by the clock subsystem when the item exits a clock-eligible state. * **`clockDelta`** — Computed elapsed duration between `clockStart` and `clockFinish`. Server-maintained. * **`clockHours`** — Convenience integer-hour view of `clockDelta`. Server-maintained. * **`clockStatus`** — `Active` / `Paused` / `Breached` / `Stopped`. Updated by the clock subsystem as the item moves through workflow states. * **`dependencyKey`** — Hierarchy path for tree views. Maintained by the dependency engine when parent/child links change. * **`dependencyIndent`** — Indent depth in the dependency tree. Server-maintained. * **`dependencyChild`** — Child ordering within the dependency tree. Server-maintained. ## Fields | Field | Type | Required | Notes | | ------------------ | ----------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `containerId` | string | **Required** | The container this item belongs to. Determines billing / cost-center inheritance. | | `processId` | string | **Required** | The process whose workflow + field schema applies to this item. | | `sequence` | integer | Server-set | Auto-incrementing per-container sequence — the `123` in `FEN-123`. Server-managed. | | `boardId` | string | Optional | Board the item appears on. Items can exist without a board until placed. | | `cycleId` | string | Optional | Cycle (sprint / iteration) the item is assigned to. | | `resourceType` | [`ResourceType`](/data-model/constants#resource-type) | Optional | Type of the assigned resource: `User` or `JobRole`. See [`ResourceType`](/data-model/constants#resource-type). | | `resourceId` | string | Optional | Id of the assigned resource. User id when `resourceType=User`; job-role id when `resourceType=JobRole`. | | `dateOpened` | string • nullable | Optional | Set on workflow transition into the open state. Pre-populate on create to backdate the open event. | | `dateWorking` | string • nullable | Optional | Set on transition into the working state. | | `dateClosed` | string • nullable | Optional | Set on transition into a terminal state. | | `time` | array\<[`ItemTime`](/data-model/item-time)> | Optional | Time entries logged against this item. See [`ItemTime`](/data-model/item-time). The `timeLogged` format is `Xh Ym` (e.g. `2h 15m`). | | `field` | array\<[`DataItemFieldValue`](/data-model/data-item-field-value)> | **Required** | Array of `DataItemFieldValue { id, data }`. Carries every human attribute — Name, Description, Status, Priority, Severity, Resolution, plus any custom fields the process declares. | | `meta` | `array<{ key: string, value: string }>` | Optional | Free-form key/value metadata for fields not modelled by the process. Lossless — useful on imports. | | `clockStart` | string (date-time) | Server-set | Server-set when the item enters a clock-eligible state. | | `clockFinish` | string (date-time) | Server-set | Server-set when the item exits a clock-eligible state. | | `clockDelta` | number | Server-set | Computed elapsed duration between `clockStart` and `clockFinish`. | | `clockHours` | integer | Server-set | Convenience integer-hour view of `clockDelta`. | | `clockStatus` | [`ClockStatusEnum`](/data-model/constants#clock-status-enum) | Server-set | Active / Paused / Breached / Stopped. See [`ClockStatusEnum`](/data-model/constants#clock-status-enum). | | `dependencyKey` | string | Server-set | Hierarchy path key for tree views. Server-managed. | | `dependencyIndent` | integer | Server-set | Indent depth in dependency tree. Server-managed. | | `dependencyChild` | integer | Server-set | Child ordering within the dependency tree. Server-managed. | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/item` * `PUT /api/item/{id}` ### Response body * `GET /api/board/{boardId}/cycle/{cycleId}/item` * `GET /api/item/{id}/dependency` * `POST /api/item/copy` * `POST /api/item/find` * `POST /api/item/list` * `POST /api/item/resource` * `POST /api/item/{id}` * `POST /api/time/resource/{userId}` ### CLI `ekso item` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataItemFieldValue Source: https://ekso.dev/data-model/data-item-field-value Sample shape for DataItemFieldValue. Sample shape for `DataItemFieldValue`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldId": "", "data": "example" } ``` ## Fields | Field | Type | Required | Notes | | --------- | ------ | ------------ | ----- | | `fieldId` | string | **Required** | | | `data` | string | Optional | | # DataLink Source: https://ekso.dev/data-model/data-link Entity-to-entity relationship. Entity-to-entity relationship. Links connect items to other items (`Subtask`, `Relates`, `Blocks`, `Duplicate`), or items to external resources via the link-type collection. Links are directional — `FromOwner` / `ToOwner` capture which side is which. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fromOwner": "Item", "fromOwnerId": "item_01HITEM_LOGIN_BUG", "toOwner": "Item", "toOwnerId": "item_01HITEM_TIMEOUT_FIX", "linkType": "Blocks" } ``` ## Fields | Field | Type | Required | Notes | | ------------- | ----------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `linkType` | string | **Required** | Relationship label. Common values: `Subtask`, `Relates`, `Blocks`, `Duplicate`. Free-text — Jira link types come through verbatim during migration. | | `fromOwner` | [`ContentOwner`](/data-model/constants#content-owner) | Optional | Type of the entity at the from-side of the link. | | `fromOwnerId` | string | **Required** | Id of the from-side entity. | | `toOwner` | [`ContentOwner`](/data-model/constants#content-owner) | Optional | Type of the entity at the to-side of the link. | | `toOwnerId` | string | **Required** | Id of the to-side entity. | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/link` ### Response body * `POST /api/link` ### CLI `ekso link` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataProcess Source: https://ekso.dev/data-model/data-process Sample shape for DataProcess. Sample shape for `DataProcess`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "processId": "", "field": [ "" ] } ``` ## Fields | Field | Type | Required | Notes | | ----------- | ------------------------------------------------------------ | -------- | ----- | | `processId` | string | Optional | | | `field` | array\<[`DataProcessField`](/data-model/data-process-field)> | Optional | | # DataProcessField Source: https://ekso.dev/data-model/data-process-field Sample shape for DataProcessField. Sample shape for `DataProcessField`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "fieldId": "", "access": [ "" ] } ``` ## Fields | Field | Type | Required | Notes | | --------- | ------------------------------------------- | -------- | ----- | | `fieldId` | string | Optional | | | `access` | array\<[`DataRole`](/data-model/data-role)> | Optional | | # DataRole Source: https://ekso.dev/data-model/data-role Sample shape for DataRole. Sample shape for `DataRole`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "role": "", "userGroup": [ "example" ] } ``` ## Fields | Field | Type | Required | Notes | | ----------- | ------------------------------------------- | -------- | ----- | | `role` | [`AppRole`](/data-model/constants#app-role) | Optional | | | `userGroup` | array\ | Optional | | # DataTime Source: https://ekso.dev/data-model/data-time Sample shape for DataTime. Sample shape for `DataTime`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "dated": "2026-04-29T14:30:00Z", "userId": "", "nonBillableId": "", "timeLogged": "example", "comment": "example" } ``` ## Fields | Field | Type | Required | Notes | | --------------- | ------------------ | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `dated` | string (date-time) | Optional | | | `userId` | string | **Required** | | | `nonBillableId` | string | **Required** | | | `timeLogged` | string | **Required** | | | `comment` | string | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/time` * `PUT /api/time/{id}` ### Response body * `POST /api/time/{id}` * `POST /api/time` * `PUT /api/time/{id}` ### CLI `ekso time` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataUser Source: https://ekso.dev/data-model/data-user Tenant user. Tenant user. Users authenticate via OAuth device flow (the CLI / SDK path) or via the web app. The `DataUser` shape carries profile information; credential storage is separate (see `DataUserAuth`). ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "email": "alex@acme.example.com", "name": "Alex Liddell", "active": true, "jobRoleId": "jobrole_01HJOBROLE_ENGINEER", "costCenterId": "costcenter_01HCOSTCENTER_PRODUCT", "userGroup": [ "group_01HGROUP_EVERYONE", "group_01HGROUP_DEV" ], "timezone": "Europe/London", "currency": "GBP", "country": "GB" } ``` ## Fields | Field | Type | Required | Notes | | --------------- | ------------------ | ------------ | ------------------------------------------------------------------------------------------------ | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `name` | string | **Required** | Display name shown across the UI. | | `initials` | string | **Required** | | | `email` | string | **Required** | User's email. Used as the matching key during migrations and as the recipient for notifications. | | `locale` | string | Optional | | | `timezone` | string | Optional | IANA timezone (e.g. `Europe/London`). Drives time-display in notifications and reports. | | `darkMode` | string | Optional | | | `costCenter` | string | Optional | | | `jobRole` | string | Optional | | | `workingHours` | string | Optional | | | `notifications` | boolean | Optional | | | `superAdmin` | boolean | Optional | | | `active` | boolean | Optional | When false, the user can't sign in but their authoring history is preserved. | | `locked` | string • nullable | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/user` * `PUT /api/user` ### Response body * `GET /api/user/{id}` * `GET /api/user` ### CLI `ekso user` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # DataWatch Source: https://ekso.dev/data-model/data-watch Sample shape for DataWatch. Sample shape for `DataWatch`. ## Sample JSON Copy, edit, paste. Server-set fields (`id`, `created`, `tenantId`, …) are omitted from the example — they're populated by the server on response. ```json theme={null} { "owner": "", "ownerId": "", "watcher": "", "watcherId": "", "type": "" } ``` ## Fields | Field | Type | Required | Notes | | ----------- | ----------------------------------------------------- | ------------ | --------------------------------------- | | `entity` | string • nullable | Server-set | Set by the server. Read-only on writes. | | `owner` | [`ContentOwner`](/data-model/constants#content-owner) | Optional | | | `ownerId` | string | **Required** | | | `watcher` | [`WatcherType`](/data-model/constants#watcher-type) | Optional | | | `watcherId` | string | **Required** | | | `type` | [`WatchType`](/data-model/constants#watch-type) | Optional | | | `tenantId` | string | Server-set | Set by the server. Read-only on writes. | | `id` | string | Server-set | Set by the server. Read-only on writes. | | `created` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `createdBy` | string | Server-set | Set by the server. Read-only on writes. | | `updated` | string (date-time) | Server-set | Set by the server. Read-only on writes. | | `updatedBy` | string | Server-set | Set by the server. Read-only on writes. | | `deleted` | boolean | Server-set | Set by the server. Read-only on writes. | ## Used by ### Request body * `POST /api/watch` * `PUT /api/watch/{id}` ### Response body * `GET /api/watch/owner/{ownerId}` * `GET /api/watch/watcher/{watcherId}` * `GET /api/watch/{id}` * `POST /api/watch` * `PUT /api/watch/{id}` ### CLI `ekso watch` — see the [command branches](/cli/branches) page for available verbs. Body for create / update verbs typically passed via `--data @file.json` matching the [sample JSON](#sample-json) above. # Ekso data models Source: https://ekso.dev/data-model/overview Reference for every shape that crosses the API, SDK, and CLI boundaries — copy-pasteable JSON examples, field tables, and constants in one place. Every shape that crosses the Ekso API / SDK / CLI boundary lives here with a copy-pasteable JSON example, a field table, and a list of where it's used. One source of truth — referenced from API endpoints, SDK methods, CLI commands, and migration guides. ## How to find what you need | You're looking at... | Start here | | -------------------------------------------------------- | ------------------------------------------------------ | | `POST /api/container` request body | [`DataContainer`](/data-model/data-container) | | `POST /api/item` request body | [`DataItem`](/data-model/data-item) | | Comments / activity on an item | [`DataAnnotation`](/data-model/data-annotation) | | File uploads | [`DataFile`](/data-model/data-file) | | Sprint / iteration / release | [`DataCycle`](/data-model/data-cycle) | | Workflow definition | [`ConfigProcess`](/data-model/config-process) | | Custom field definitions | [Fields group](#core-models) — one page per field type | | Automation rules | [`ConfigRule`](/data-model/config-rule) | | `"owner": 30` — what does that mean? | [Constants](/data-model/constants#content-owner) | | Migrating from Jira / Linear / DevOps / Zendesk / Gemini | [Migrate guides](/cli/migrate/overview) | If you're new to Ekso's domain, start with the [Guides → Core concepts](/guide/concepts/overview) — it explains what a container, item, board, and cycle are *before* you have to read their JSON shape. ## Anatomy of a page Every model page has the same four sections: 1. **Sample JSON** — a complete, realistic request body you can copy and edit. Server-set fields (see below) are omitted from the example so the JSON shows what *you* send, not what comes back. 2. **What Ekso fills in** — for models with significant server-managed state ([`DataItem`](/data-model/data-item) is the obvious one), a callout listing fields you'll receive in the response that you didn't pass on the request. 3. **Fields** — every property with type, required-or-not, and notes. Cross-references link to other model pages. 4. **Used by** — request and response endpoints that take or return this shape, plus the matching CLI branch. ## Conventions Things that hold across every page in this section. ### System fields are server-set Every model carries the same eight system fields — they're set by the server, not by you, and don't appear in request examples: | Field | Set when | | ----------------------- | ---------------------------------------------------------------------- | | `id` | Created on POST | | `tenantId` | Inherited from the request's auth context | | `entity` | Constant per model — identifies the type in mixed-shape responses | | `created` / `createdBy` | Stamped on POST | | `updated` / `updatedBy` | Stamped on POST and on every PUT/PATCH | | `deleted` | Soft-delete flag — toggled by DELETE; non-deleted records have `false` | Some models layer additional server-managed fields on top — `DataItem.sequence`, `DataItem.clockStart`, `DataContainer.lastSequence` and so on. Each model page calls these out in its **What Ekso fills in** section. ### Required vs Optional comes from validators The OpenAPI spec marks every property optional (a quirk of how `System.Text.Json` defaults serialise `.NET` records). The authoritative source for required-ness is the server-side validator that runs before any write. The field table on each page reflects validator rules — `**Required**` means the API will reject your request with a `422` if you omit it. ### Enums on the wire are integers Enums travel as integers over HTTP and in JSON. The SDK exposes them as typed enums (compiled-in safety). The CLI accepts either the integer or the variant name depending on the command — check `--help`. The single source of truth for every enum's variant names and values is the [Constants](/data-model/constants) page. Schema field tables link directly into the right anchor — for example, `[ContentOwner](/data-model/constants#content-owner)` jumps you straight to the value list. ### Meta is the lossless free-form bag Several models carry a `meta` array for free-form key/value metadata that doesn't fit a typed field. Renders in field tables as `array<{ key: string, value: string }>`. Used heavily by the migration tool to preserve source-platform data that has no first-class Ekso shape (e.g. `meta.jira_affects_versions[]`). ### `DataItem.Field[]` is the value bag for typed fields Unlike most models, `DataItem` keeps almost no human attributes as top-level properties. Name, Description, Status, Priority, Severity, Resolution — all live in `field[]` as `DataItemFieldValue { id, data }` entries. The `id` references a process-defined field (system or custom); the `data` is the value. See [`DataItem`](/data-model/data-item) and [`DataItemFieldValue`](/data-model/data-item-field-value) for the full shape. ## Where to next * **[Constants](/data-model/constants)** — every enum referenced by the data models, in one place. * **Core models** — [`DataContainer`](/data-model/data-container) · [`DataItem`](/data-model/data-item) · [`DataAnnotation`](/data-model/data-annotation) · [`DataFile`](/data-model/data-file) · [`DataLink`](/data-model/data-link) · [`DataBoard`](/data-model/data-board) · [`DataCycle`](/data-model/data-cycle) · [`DataArea`](/data-model/data-area) * **[API Reference](/api-reference/introduction)** — the underlying HTTP routes that take and return these shapes. * **[CLI](/cli/overview)** — `ekso` commands that operate on these models. * **[SDK](/sdk/overview)** — typed C# client wrapping the same surface. * **[Migrate](/cli/migrate/overview)** — pull data from Jira / Linear / Azure DevOps / Zendesk / Gemini into these shapes. # Agent onboarding Source: https://ekso.dev/guide/agent-onboarding Programmatically complete the first-run /startup wizard on a fresh self-host Ekso install — designed for AI agents acting on behalf of an operator. ## Overview The `/startup` API lets AI agents (Claude, GPT, Cursor, etc.) drive Ekso's first-run setup wizard — the same outcome as a human filling out the wizard form, but via two anonymous JSON endpoints. After the wizard succeeds the install has its first tenant, an admin user, the auth config, and (optionally) an AI provider — ready to log into. Self-host means **the agent is operating against the customer's own install**, not a hosted ekso.app endpoint. The customer has already: 1. Acquired a license — [ekso.app/get-started](https://ekso.app/get-started) emails a Free-tier JWT (3 users, no credit card); paid tiers with unlimited users come from [ekso.app/pricing](https://ekso.app/pricing) via Stripe checkout. This is its own one-shot flow today; future agent automation for this step is on the roadmap. 2. Stood up the install — Docker, behind whatever DNS the customer chose (e.g. `https://ekso.acme.com`). See [Install](/guide/install/quickstart). What the agent then automates is **Step 3**: drive the install's `/startup` wizard with the operator's details (name, email, password, organization, public URL) plus the license JWT. The install bootstraps the tenant and the wizard URL closes (410 Gone) on success. ## Endpoints Both endpoints are anonymous (no auth header), live on the customer's install URL, and are gated by `StartupGuard` middleware — they return **410 Gone** once the install has at least one tenant. There is no email-verification step: the operator running the agent has direct host access to the freshly-bootstrapped install already, so abuse-prevention is solved by network reachability rather than email round-trips. ### Step 1 (optional): Validate the public URL Before posting the wizard, the agent can sanity-check the URL the customer plans to use. Permissive — format check only: ```bash theme={null} POST https://ekso.acme.com/api/startup/url Content-Type: application/json { "publicUrl": "https://ekso.acme.com" } ``` **Success (200):** ```json theme={null} { "message": "OK", "url": "https://ekso.acme.com" } ``` **Failure (400):** `{ "error": "Invalid URL format" }` / `"URL must be http:// or https://"` / `"URL must include a host"`. ### Step 2: Run the wizard ```bash theme={null} POST https://ekso.acme.com/api/startup/go Content-Type: application/json { "name": "Jane Smith", "email": "jane@acme.com", "password": "", "organization": "Acme Corp", "publicUrl": "https://ekso.acme.com", "activation": "", "aiProvider": "openai", "aiModel": "gpt-5.5", "aiKey": "sk-proj-..." } ``` **Success (200):** ```json theme={null} { "tenantId": "a1b2c3d4e5f6", "message": "Install configured" } ``` After this returns, `/api/startup/*` flips to 410 Gone — the install is configured. The agent should hand the operator the install URL and the email it was bootstrapped with so they can log in. ## Request fields | Field | Required | Notes | | -------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | Yes | Admin user's full name. | | `email` | Yes | Admin user's email. Receives any password-reset / future-notification mail. | | `password` | Yes | 8–64 characters. Stored hashed; never recoverable. | | `organization` | Yes | Tenant display name (e.g. "Acme Corp"). | | `publicUrl` | Yes | The `PublicUrl` the install is reachable at. Must match the host the agent is POSTing to. Used in outbound emails and OAuth redirect URIs. | | `activation` | Optional but recommended | License JWT — Free tier from [ekso.app/get-started](https://ekso.app/get-started), paid tiers from [ekso.app/pricing](https://ekso.app/pricing). Empty = install runs in Free-tier defaults until pasted later via Settings → Account. | | `aiProvider` | Optional | `""` (disabled), `"openai"`, or `"anthropic"`. Sets up `ConfigAI` so vector embeddings during sample-data onboarding work without a separate Settings step. | | `aiModel` | Conditional | Required when `aiProvider` is set. Suggested defaults: `gpt-5.5` (OpenAI), `claude-sonnet-4-6` (Anthropic). | | `aiKey` | Conditional | Required when `aiProvider` is set. Empty key with non-empty provider is rejected (would silently no-op every AI call). | ## Error responses All failures return a structured error body: ```json theme={null} { "error": "Missing or invalid fields" } ``` | HTTP Status | Cause | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | Invalid input — empty name/organization/publicUrl, malformed email, password too short/long, malformed PublicUrl, AI provider/key inconsistency. The error body's `error` string identifies the specific failure. | | `410 Gone` | Install already configured. The wizard is closed and `/api/startup/*` is no longer reachable. The agent should hand the operator the existing login URL instead of trying again. | | `429` | Rate limited (the `Startup` rate-limit policy gates the endpoint). Back off and retry. | ## Example agent conversation ``` Agent: I'll bootstrap your Ekso install. I'll need your name, email, a password (8–64 chars), your organization name, and your install's public URL. Do you have a license JWT from ekso.app/get-started (Free) or ekso.app/pricing (paid)? User: Jane Smith, jane@acme.com, password "S3cure!Pass", Acme Corp, https://ekso.acme.com. Yes — here's the JWT: eyJhbGciOi... Agent: [POST https://ekso.acme.com/api/startup/url with the publicUrl] URL looks good. Want me to enable AI features now (OpenAI or Anthropic)? You can also configure this later in Settings. User: Yes — OpenAI, here's my key: sk-proj-... Agent: [POST https://ekso.acme.com/api/startup/go with everything] ✓ Install configured. Log in at https://ekso.acme.com with jane@acme.com. The setup wizard is now closed. ``` ## What the wizard actually does `POST /api/startup/go` delegates to the `IInstallSetup` workhorse, which atomically: 1. Creates the install-default `DataTenant` with the supplied organization name + public URL. 2. Creates the admin `DataUser` (super admin, hashed password). 3. Seeds default groups, permissions, and the forms-auth config. 4. Stores the license JWT in `DataTenant.Activation` (verified later by `LicenseProvider`). 5. If `aiProvider` is set, writes `ConfigAI` so the next request sees AI enabled. 6. Enqueues a background sample-data onboarding job to populate the tenant with example items, fields, and processes. On success, `IInstallConfigured.MarkConfigured()` flips an in-process flag that `StartupGuard` reads to refuse subsequent wizard calls — making the bootstrap genuinely one-shot. ## Discovery For self-host, agents can discover the install's onboarding API through the install itself once it's reachable: * **`/api/startup/url`** and **`/api/startup/go`** — the canonical paths, documented here. Both return 410 once configured, so an agent that pings these can also detect "this install is already set up" before prompting the operator for credentials. * **OpenAPI** — `https://ekso.acme.com/api/openapi/v1.json` (the Backend serves the same spec the public docs render). * **This page** — `https://ekso.dev/guide/agent-onboarding` is the public-facing reference. `llms.txt` and `agent-card.json` discovery for the SaaS-era hosted onboarding endpoints (`/api/bo/on/agent/*`) are gone — those endpoints were removed when the BackOfficeService deleted in Lane G of the [self-host pivot](/plans/self-hosted-on-premise). A self-host install can publish its own `/.well-known/agent-card.json` advertising the `/api/startup/*` endpoints; this is on the roadmap. # Items and fields Source: https://ekso.dev/guide/api/items-and-fields Understand the DataItem schema, field system, and permission model — discover available fields and build API requests. This guide explains how items and fields work at the API level. It covers the JSON shape of an item, how to discover which fields are available for a given container and process, and how the permission model affects what you can read and write. For the conceptual overview, see [Items](/guide/concepts/items), [Fields](/guide/concepts/fields), and [Processes](/guide/concepts/processes). ## DataItem schema Every work item in Ekso is a **DataItem**. When you fetch or create an item through the API, the JSON looks like this: ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "containerId": "f9e8d7c6-b5a4-3210-fedc-ba0987654321", "processId": "d4c3b2a1-0987-6543-2109-876543210fed", "sequence": 42, "boardId": "b0a1b2c3-d4e5-f678-90ab-cdef01234567", "cycleId": "c1d2e3f4-a5b6-7890-cdef-1234567890ab", "resourceType": 1, "resourceId": "u1234567-89ab-cdef-0123-456789abcdef", "dateOpened": "2025-11-01T09:00:00Z", "dateWorking": "2025-11-02T14:30:00Z", "dateClosed": null, "field": [ { "fieldId": "Name", "data": "Fix login timeout" }, { "fieldId": "Priority", "data": "p-high-id" }, { "fieldId": "Status", "data": "s-working-id" }, { "fieldId": "OwnedBy", "data": "u1234567-89ab-cdef-0123-456789abcdef" }, { "fieldId": "Description", "data": "Users report a 30-second delay on the login page." } ], "time": [], "meta": [], "created": "2025-11-01T09:00:00Z", "createdBy": "u1234567-89ab-cdef-0123-456789abcdef", "updated": "2025-11-03T11:15:00Z", "updatedBy": "u1234567-89ab-cdef-0123-456789abcdef" } ``` ### Top-level properties | Property | Type | Description | | -------------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | `id` | string | Unique item identifier. | | `containerId` | string | The [container](/guide/concepts/containers) this item belongs to. | | `processId` | string | The [process](/guide/concepts/processes) that defines the item's workflow and fields. | | `sequence` | integer | Auto-incremented number within the container. Combined with the container key to form the item key (e.g., `GEM-42`). | | `boardId` | string | The [board](/guide/concepts/boards) the item is planned on, if any. | | `cycleId` | string | The [cycle](/guide/concepts/cycles) within the board, if any. | | `resourceType` | integer | `0` = unassigned, `1` = User, `2` = JobRole. | | `resourceId` | string | ID of the assigned user or job role. | | `dateOpened` | datetime | When the item was opened. | | `dateWorking` | datetime | When work began. | | `dateClosed` | datetime | When the item was closed. | | `field` | array | Field values — see [the field array](#the-field-array) below. | | `time` | array | Time log entries. See [Time tracking](/guide/concepts/time-tracking). | | `meta` | array | Key-value metadata pairs. | | `created` | datetime | Record creation timestamp. | | `createdBy` | string | User ID of the creator. | | `updated` | datetime | Last update timestamp. | | `updatedBy` | string | User ID of the last updater. | ### The field array The `field` array is where most of the item's data lives. Each entry is a simple pair: | Property | Type | Description | | --------- | ------ | ---------------------------------------------------------------------------------------------------------- | | `fieldId` | string | References a field definition. Can be a system field name (e.g., `Name`, `Priority`) or a custom field ID. | | `data` | string | The field's value, always stored as a string regardless of the field type. | All field values are strings. A date field stores `"2025-12-15T00:00:00Z"`, an integer stores `"42"`, a toggle stores `"true"` or `"false"`. Your integration must interpret the string according to the field's type — use the [screen endpoint](#the-screen-endpoint) to get the full field definition alongside each value. ## Field definitions The `GET /api/field` endpoint returns every field definition in your tenant. Each field definition describes the field's type, validation rules, and configuration. ### Field types Ekso supports ten field types. Each type serializes its value as a string in the `data` property: | Type | Description | Example `data` value | | ------- | --------------------------------------------------------- | ------------------------- | | Text | Plain text, multiline, or rich text. | `"Fix the login timeout"` | | List | Single or multi-select from predefined values. | `"list-value-id"` | | Picker | References another entity (user, container, board, etc.). | `"entity-id"` | | Date | UTC date/time. | `"2025-12-15T00:00:00Z"` | | Integer | Whole number within min/max bounds. | `"42"` | | Decimal | Decimal number within min/max bounds. | `"99.50"` | | Percent | Percentage value (0–100 by default). | `"75"` | | Time | Duration (days, hours, minutes). | `"02:30"` | | Toggle | Boolean on/off. | `"true"` | For a full explanation of each type and the system fields, see [Fields](/guide/concepts/fields). ### Key field properties Every field definition includes these properties: | Property | Type | Description | | -------------- | ------- | -------------------------------------------------------------------------------------------- | | `id` | string | Unique field identifier. | | `name` | string | Field name, used as the `fieldId` in item data (e.g., `Name`, `Priority`). | | `prompt` | string | Display label for the field. | | `description` | string | Admin-facing description of the field's purpose. | | `fieldType` | string | One of: `Text`, `List`, `Picker`, `Date`, `Integer`, `Decimal`, `Percent`, `Time`, `Toggle`. | | `defaultValue` | string | Default value applied when creating an item. | | `isCore` | boolean | `true` for system fields, `false` for custom fields. | | `placement` | array | Where the field appears — `Process` (item forms) or `Board` (board views). | | `usage` | array | How the field is used — `System`, `Input`, `Filter`, `Condition`, `Action`. | ### Picker scopes Picker fields reference a specific entity type. The `scope` property tells you what kind of entity the picker selects: | Scope | Picks from | | ------------ | ------------------------------------- | | `User` | Workspace members | | `Container` | Containers (projects, products, etc.) | | `Area` | Areas within a container | | `Board` | Planning boards | | `Cycle` | Cycles within a board | | `Process` | Process types | | `Resource` | Users and job roles | | `UserGroup` | Security groups | | `CostCenter` | Cost centers | | `Crm` | CRM / customer records | | `Sku` | Billable time codes | | `JobRole` | Job roles | ### List field values List fields (e.g., Priority, Severity, Status, Resolution) have a `data` array containing the valid options: ```json theme={null} { "fieldType": "List", "name": "Priority", "data": [ { "id": "p-critical", "value": "Critical", "image": "", "metadata": "", "active": true }, { "id": "p-high", "value": "High", "image": "", "metadata": "", "active": true }, { "id": "p-medium", "value": "Medium", "image": "", "metadata": "", "active": true }, { "id": "p-low", "value": "Low", "image": "", "metadata": "", "active": true } ] } ``` | Property | Type | Description | | ---------- | ------- | -------------------------------------------------------------------------------------------------------------------- | | `id` | string | The value you send in the item's `data` property. | | `value` | string | Display text. | | `image` | string | Optional icon path. | | `metadata` | string | Additional metadata — for Status fields, this indicates the lifecycle verb (`Open`, `Working`, `Closed`, `Blocked`). | | `active` | boolean | `false` means the value is retired — it won't appear in selection lists but existing items retain it. | When setting a list field value, always send the list item `id`, not the display text. For example, send `"p-high"` not `"High"`. ## Process field configuration A [process](/guide/concepts/processes) controls which fields appear on an item and whether each field is required. The `GET /api/process` endpoint returns process definitions, each with a `fields` array: ```json theme={null} { "id": "proc-defect-id", "name": "Defect", "fields": [ { "fieldId": "Name", "order": 1, "required": true }, { "fieldId": "Description", "order": 2, "required": false }, { "fieldId": "Priority", "order": 3, "required": true }, { "fieldId": "Severity", "order": 4, "required": true }, { "fieldId": "Status", "order": 5, "required": true }, { "fieldId": "OwnedBy", "order": 6, "required": false }, { "fieldId": "custom-field-id", "order": 7, "required": false } ] } ``` | Property | Type | Description | | ---------- | ------- | ---------------------------------------------------------------------- | | `fieldId` | string | References the field definition. | | `order` | integer | Display order on item forms. | | `required` | boolean | Whether the field must have a value when creating or updating an item. | The same field can be required in one process and optional in another. For example, Severity might be required for Defects but optional for Change Requests. Every process always includes certain system fields — Name, Description, Status, OwnedBy, RestrictedTo, and audit fields — even if they're not listed in the `fields` array. These are always present on every item. ## The screen endpoint The `POST /api/item/screen` endpoint is the primary discovery tool for API consumers. It combines field definitions, current values, required flags, and permission filtering into a single response — giving you exactly the fields the current user can work with. ### Request Send an `ItemScreenRequest`: ```json theme={null} { "containerId": "CONTAINER_ID", "processId": "PROCESS_ID", "itemId": "", "stage": 2, "source": "Api" } ``` | Property | Type | Required | Description | | ------------- | ------- | -------- | --------------------------------------------------------------------- | | `containerId` | string | Yes | The container the item belongs to (or will be created in). | | `processId` | string | Yes | The process type. | | `itemId` | string | No | The item ID for existing items. Leave empty when creating a new item. | | `stage` | integer | Yes | The screen stage — determines which permission column to evaluate. | | `source` | string | No | Origin of the request. Use `"Api"` for API integrations. | ### Screen stages The `stage` parameter controls which permission column the endpoint evaluates: | Stage | Value | Use when... | | ------ | ----- | --------------------------------------------------------------- | | See | `1` | Viewing an item — returns fields the user can read. | | Set | `2` | Creating an item — returns fields the user can set on creation. | | Change | `3` | Updating an item — returns fields the user can modify. | ### Response The endpoint returns an `ItemScreenModel`: ```json theme={null} { "item": { }, "stage": 2, "workflow": [ { "id": "s-open", "value": "Open", "metadata": "Open", "active": true }, { "id": "s-working", "value": "In Progress", "metadata": "Working", "active": true } ], "field": [ { "definition": { "id": "field-name-id", "name": "Name", "prompt": "Name", "fieldType": "Text", "defaultValue": "", "isCore": true }, "data": "", "required": true }, { "definition": { "id": "field-priority-id", "name": "Priority", "prompt": "Priority", "fieldType": "List", "defaultValue": "p-medium", "isCore": true, "data": [ { "id": "p-critical", "value": "Critical", "active": true }, { "id": "p-high", "value": "High", "active": true }, { "id": "p-medium", "value": "Medium", "active": true }, { "id": "p-low", "value": "Low", "active": true } ] }, "data": "p-medium", "required": true } ] } ``` | Property | Type | Description | | ---------- | ------- | ------------------------------------------------------------------------------------------------------------ | | `item` | object | The full item record (blank defaults for stage `Set`). | | `stage` | integer | The screen stage that was evaluated. | | `workflow` | array | Valid next statuses from the item's current state — only populated for stages `Set` and `Change`. | | `field` | array | Ordered list of fields the user can access, each with its full definition, current value, and required flag. | Each entry in the `field` array contains: | Property | Type | Description | | ------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `definition` | object | The full polymorphic field definition — includes type-specific properties like `data` for List fields, `scope` for Picker fields, or `maximum` for Text fields. | | `data` | string | The field's current value (or default value for new items). | | `required` | boolean | Whether this field must be provided. | The screen endpoint only returns fields the current user has permission to access. If a field is missing from the response, the user's group does not have the required access level for that field at the requested stage. ## Field-level permissions Each container can configure per-field permissions for every process. Three access levels control who can interact with each field: | Access level | Stage | Controls | | ------------ | ----- | ------------------------------------------------------------ | | See | `1` | Which user groups can view the field value. | | Set | `2` | Which user groups can provide a value when creating an item. | | Change | `3` | Which user groups can modify the value after creation. | By default, all three levels are set to **Everyone** — all users in the workspace can see, set, and change every field. Admins can restrict any level to specific [user groups](/guide/concepts/access-control). The screen endpoint respects these permissions automatically. When you call it with a particular stage, the response includes only the fields that the authenticated user's groups have access to for that stage. For the conceptual explanation and configuration details, see [Field-level access control](/guide/concepts/processes#field-level-access-control). If you send field values for fields the user cannot edit, the API will reject the request. Always use the screen endpoint to discover which fields are available before building your request payload. ## Creating an item via the API Call the screen endpoint with stage `Set` to find out which fields the current user can provide: ```bash theme={null} curl -X POST https://ekso.acme.com/api/item/screen \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "containerId": "CONTAINER_ID", "processId": "PROCESS_ID", "itemId": "", "stage": 2, "source": "Api" }' ``` The response `field` array tells you every field you can set, its type, default value, and whether it's required. Using the screen response, build your `field` array. Include all required fields and any optional fields you want to set: ```json theme={null} [ { "fieldId": "Name", "data": "Fix login timeout" }, { "fieldId": "Priority", "data": "p-high" }, { "fieldId": "Severity", "data": "s-major" }, { "fieldId": "Description", "data": "Users report a 30-second delay on the login page." } ] ``` Use the field definition's `name` as the `fieldId`. For List and Picker fields, use the list item `id` or entity `id` as the `data` value. Send a `POST` to `/api/item` with the container, process, and field values: ```bash theme={null} curl -X POST https://ekso.acme.com/api/item \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "containerId": "CONTAINER_ID", "processId": "PROCESS_ID", "field": [ { "fieldId": "Name", "data": "Fix login timeout" }, { "fieldId": "Priority", "data": "p-high" }, { "fieldId": "Severity", "data": "s-major" }, { "fieldId": "Description", "data": "Users report a 30-second delay on the login page." } ] }' ``` The response includes the created item with its generated key (e.g., `GEM-42`), all field values, and default values applied by the system. If required fields are missing or values are invalid, the API returns a `400` with details: ```json theme={null} { "kind": "Validation", "message": "One or more fields failed validation.", "fields": [ { "field": "Name", "code": "required" } ] } ``` Check the `fields` array to identify which values need correction. ## Updating an item via the API Retrieve the current item by ID: ```bash theme={null} curl -X POST https://ekso.acme.com/api/item/ITEM_ID \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "containerId": "CONTAINER_ID" }' ``` Call the screen endpoint with stage `Change` to find which fields the user can modify: ```bash theme={null} curl -X POST https://ekso.acme.com/api/item/screen \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "containerId": "CONTAINER_ID", "processId": "PROCESS_ID", "itemId": "ITEM_ID", "stage": 3, "source": "Api" }' ``` The response includes each field's current value in the `data` property. The `workflow` array shows the valid next statuses. Send a `PUT` to `/api/item/{id}` with the updated field values: ```bash theme={null} curl -X PUT https://ekso.acme.com/api/item/ITEM_ID \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "containerId": "CONTAINER_ID", "processId": "PROCESS_ID", "field": [ { "fieldId": "Status", "data": "s-working" }, { "fieldId": "Priority", "data": "p-critical" } ] }' ``` Only include the fields you want to change. The API merges your updates with the existing field values. ## Next steps Full reference for system fields, custom field types, and default values. How processes define workflows, field configuration, and access control. Complete endpoint documentation with request and response schemas. # API overview Source: https://ekso.dev/guide/api/overview Understand the Ekso REST API — base URL, OAuth 2.0 authentication, error handling, and response conventions. The Ekso API is a REST API that lets you manage items, boards, containers, time entries, and every other resource in your tenant programmatically. This page covers the architecture — for a hands-on walkthrough, see the [Quickstart](/guide/api/quickstart). ## Base URL All API requests target your install's URL — the `PublicUrl` you set during the `/startup` wizard: ```text theme={null} https://ekso.acme.com/api/ ``` Replace `ekso.acme.com` with your install's hostname. Every endpoint described in the [API reference](/api-reference/introduction) is relative to this base. ## Authentication Ekso uses **OAuth 2.0 with PKCE** (Proof Key for Code Exchange). PKCE is the recommended flow for public clients — it protects against authorization code interception without requiring a client secret. ### Discovery Fetch the OAuth metadata from your install's well-known endpoint: ```bash theme={null} curl https://ekso.acme.com/.well-known/oauth-authorization-server ``` This returns the authorization, token, and registration endpoints you need for the flow. ### Flow summary 1. **Generate a code verifier and challenge** — create a random string (verifier) and its SHA-256 hash (challenge) 2. **Redirect to the authorization endpoint** — include the code challenge, a redirect URI, and requested scopes 3. **User approves** — Ekso redirects back to your app with an authorization code 4. **Exchange code for tokens** — POST the authorization code and code verifier to the token endpoint 5. **Use the access token** — include it in the `Authorization` header of every API request See the full [OAuth 2.0 details](/api-reference/introduction#oauth-20) in the API reference. ### Using the token Add the access token to every request as a Bearer token: ```bash theme={null} curl https://ekso.acme.com/api/board \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` ## Error format All errors follow a consistent JSON structure: ```json theme={null} { "kind": "Validation", "message": "Human-readable error description", "fields": [ { "field": "containerId", "code": "required" } ] } ``` ### Error kinds | Status | Kind | Meaning | | ------ | ------------ | --------------------------------------------------------------------------- | | 400 | `Validation` | One or more fields failed validation — check the `fields` array for details | | 403 | `Permission` | The authenticated user lacks the required permissions | | 422 | `Logic` | A business rule was violated (e.g., closing an already-closed cycle) | | 500 | `Exception` | An unexpected server error — contact support if this persists | The `fields` array is only present on `Validation` errors. Each entry identifies the field name and a machine-readable error code. ## Response conventions * **Lists** return JSON arrays directly (e.g., `GET /api/board` returns `[{...}, {...}]`) * **Single resources** return a JSON object (e.g., `GET /api/board/{id}` returns `{...}`) * **Creates and updates** return the full resource after the operation * **Deletes** return `204 No Content` on success * All response bodies use **camelCase** property names ## Token lifetimes and limits When building integrations, keep these OAuth token lifetimes and client limits in mind: | Setting | Value | | ---------------------------- | ------------------------------------------ | | Access token lifetime | 15 minutes | | Authorization code lifetime | 5 minutes | | Refresh token lifetime | 30 days | | PKCE code challenge method | S256 | | Max OAuth clients per tenant | 50 | | Client staleness threshold | 90 days (unused clients may be cleaned up) | Access tokens expire after 15 minutes. Your integration should use the refresh token to obtain a new access token before the current one expires. Refresh tokens are valid for 30 days — if a refresh token expires, the user must re-authorize. ## Next steps Authenticate and make your first API call in minutes. Explore the full REST API with all endpoints and schemas. Connect AI agents directly to Ekso — no REST calls needed. # Quickstart Source: https://ekso.dev/guide/api/quickstart Authenticate and make your first Ekso API call — list boards, create an item, and handle errors. This guide walks you through authenticating with the Ekso API and making your first requests. For details on API architecture, error formats, and response conventions, see the [API overview](/guide/api/overview). ## Base URL All API requests target your install's URL — the `PublicUrl` you set during the `/startup` wizard: ```text theme={null} https://ekso.acme.com/api/ ``` Replace `ekso.acme.com` with your install's hostname. ## Authentication Ekso uses **OAuth 2.0 with PKCE**. The flow works like this: Fetch the OAuth metadata from your install: ```bash theme={null} curl https://ekso.acme.com/.well-known/oauth-authorization-server ``` This returns the authorization, token, and registration endpoints. Direct the user to the authorization endpoint with a PKCE code challenge. After the user approves, exchange the authorization code for an access token at the token endpoint. See the full [OAuth 2.0 details](/api-reference/introduction#oauth-20) in the API reference. Add the token to every API request: ```bash theme={null} curl https://ekso.acme.com/api/board \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` ## List your boards Once authenticated, list the boards visible to your account: ```bash theme={null} curl https://ekso.acme.com/api/board \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` ```json Response theme={null} [ { "entity": "b1a2c3d4-...", "name": "Q1 Sprint Board", "description": "Main delivery board for Q1", "budgetHours": 400, "locked": false, "archived": false } ] ``` ## Create an item Items are the core unit of work in Ekso. Each item belongs to a container and follows a process that defines its workflow. To create an item, you need a `containerId` and a `processId`. List your containers first: ```bash theme={null} curl https://ekso.acme.com/api/container \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` Then create the item: ```bash theme={null} curl -X POST https://ekso.acme.com/api/item \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "containerId": "CONTAINER_ID", "processId": "PROCESS_ID", "field": [ { "fieldId": "Name", "value": "Fix login timeout" } ] }' ``` The response includes the item's key (e.g., `GEM-123`), status, and all field values. Items carry a dynamic set of fields controlled by their process and container permissions. To understand the full field model, discover available field types, and use the screen endpoint for permission-aware field discovery, see [Items and fields](/guide/api/items-and-fields). ## Error handling If something goes wrong, the API returns a JSON error with a `kind`, `message`, and optional `fields` array: ```json theme={null} { "kind": "Validation", "message": "Human-readable error description", "fields": [ { "field": "containerId", "code": "required" } ] } ``` Common status codes: `400` (validation), `403` (permission), `422` (business logic), `500` (server error). See the [API overview](/guide/api/overview#error-format) for the full error reference. ## Next steps Authentication details, error format, and response conventions. Full REST API with all endpoints and schemas. Connect AI agents directly to Ekso — no REST calls needed. # Access control Source: https://ekso.dev/guide/concepts/access-control Manage permissions across your tenant with application-level access control areas, roles, and user groups. ## Overview Ekso has two layers of access control: 1. **Application-level** — controls who can access major areas of the application (boards, insights, finance, etc.) 2. **Container and process-level** — controls who can interact with specific containers, items, and fields This page covers application-level access control. For container and process-level permissions, see [Containers](/guide/concepts/containers#access-control). You manage application-level access control under **Settings > Access Control**. ## Access control areas Access control is organized into six areas, each representing a major section of the application: | Area | What it controls | | --------- | ----------------------------------------------------------------------------------------------- | | Container | Access to containers and the items within them | | Board | Access to boards, cycles, and planning features | | Insights | Access to reports and analytics dashboards | | Finance | Access to financial configuration — job roles, cost centers, SKUs, CRM, and budget reason codes | | Docs | Access to the documentation module | | Timesheet | Access to timesheet views and time entry management | Each area has its own set of role assignments. This means you can grant a user group full access to boards while restricting their access to finance settings. ## Roles Roles define what a user group can do within an access control area. The available roles are: ### Standard roles | Role | Permission | | ---------- | ----------------------------------------- | | View | Can view content in the area | | Add | Can create new content in the area | | Change | Can modify any content in the area | | Change Own | Can modify only content they created | | Delete | Can remove any content in the area | | Delete Own | Can remove only content they created | | Filter | Can filter and search content in the area | | Manage | Full administrative control over the area | ### Area-specific roles | Role | Applies to | Permission | | ---- | ---------- | ------------------------------------- | | Plan | Board | Can assign items to boards and cycles | ### Field-level roles These roles apply to process field access within containers: | Role | Permission | | ------------ | ---------------------------------------------- | | Field View | Can see the field value | | Field Add | Can set the field value when creating an item | | Field Update | Can change the field value on an existing item | Field-level roles are configured per process within each container, not at the application level. See [field-level access control](/guide/concepts/processes#field-level-access-control) for details. ## User groups Access control assignments are made to **user groups**, not individual users. Ekso creates two built-in groups during tenant setup: | Group | Description | | -------- | ------------------------------------------------------------------ | | Everyone | Automatically includes all users in the tenant. Cannot be deleted. | | Nobody | An empty group. Use it to explicitly deny access to an area. | You can create additional custom groups to represent teams, departments, or permission tiers — for example, "Developers", "Support", "Team Leads", or "Finance Admins." ### How groups work * A user can belong to multiple groups * Permissions are additive — if any of a user's groups grants a role, the user has that permission * There is no explicit "deny" — remove the group assignment to revoke access ## Default permissions When a new tenant is created, Ekso grants the **Everyone** group the following roles across all six areas: | Role | Granted by default | | ------ | ------------------ | | View | Yes | | Add | Yes | | Manage | Yes | This gives all users basic access to every area out of the box. Admins can then tighten permissions by reassigning roles to more specific groups. Start with the defaults and restrict as needed. It is easier to remove permissions from a broad grant than to troubleshoot why users cannot access features. ## System rules Some access control rules are marked as **system** rules. These are essential for the application to function and cannot be deleted. You can change which user groups are assigned to system rules, but you cannot remove the rules themselves. ## How it all fits together Application-level access control works alongside container and process-level permissions: 1. **Application-level** determines whether a user can access the area at all (e.g., can they see boards?) 2. **Container-level** determines what they can do within a specific container (e.g., can they add items to this container?) 3. **Process field-level** determines which fields they can see, set, and change on items of a specific type A user needs permissions at all three levels to interact with an item. For example, to change a field on an item: * They need **Board > View** (application level) to see the board * They need **Change** on the container (container level) to edit items * They need **Field Update** on the specific field (process level) to modify that field value # AI Source: https://ekso.dev/guide/concepts/ai Use the built-in AI assistant to ask questions, search by meaning, and get context-aware answers from your workspace data. ## Overview Ekso includes a built-in AI assistant that can answer questions about your workspace, return structured data, and perform actions — all through natural language. The AI is available from the **Ask** tab in [Insights](/guide/concepts/insights). Ekso provides a default AI provider out of the box — you can optionally bring your own OpenAI or Azure OpenAI API key for direct API access. The assistant draws on your workspace data and any [documents](/guide/concepts/docs) marked with AI Context to provide relevant, grounded answers. ## Chat interface The AI chat interface sits at the bottom of the Ask tab. Type a question in the input field and click **Ask** or press Enter to submit it. The toolbar next to the input provides: | Button | Purpose | | -------------------- | --------------------------- | | **Ask** | Submit the current question | | **+** | Start a new conversation | | History (clock icon) | Open conversation history | | Info (i icon) | View sample questions | ### Structured responses The AI returns answers as formatted text, structured tables, or a combination of both. For example, asking "Show me most profitable items" returns a table with columns for Item, Item name, Container, Board, Billable hours, Cost, Revenue, and Profit — the same data you would see in the [Item report](/guide/concepts/insights#item), surfaced through a natural-language query. ### Conversation history Conversations are saved per user. Click the history button (clock icon) to open the **History** panel, which lists your past conversations with the opening question and date. Click a conversation to continue it, or click the delete button to remove it. Each conversation maintains its full message history, so the AI can reference earlier questions and answers in the same thread. ## What you can ask The AI understands questions across your workspace. Open the info panel (i icon) to see sample questions, or type your own. You can specify date ranges, container names, board names, cycle names, and other filters in natural language. ### Configuration and setup Ask about how your workspace is configured: * *Which processes are accepted by the Fenrise container?* * *Which fields are present in the Defect process as used by the Fenrise container?* * *List workflow status values and their type* * *Which process types exist?* * *Show me the Defect process definition* * *Check the workflow transition for the Defect process to see if it can be improved, given the status list for the system* ### Financial and profitability data Query cost, revenue, and margin figures: * *Show me most profitable items* * *Show me revenue, profit, cost numbers for November and December* * *What is the mark-up breakdown for job roles?* * *Which container brought in the most revenue?* * *Show me revenue, profit, cost numbers for last month, for job roles that contain "Analyst" or "Developer"* ### Time and utilization Explore how time is being spent: * *Get me hours logged by time code for this month* * *Get time logged per week for the last two months* * *Get hours logged, billable, non-billable per user* * *Which job roles are doing the most work?* * *Who was over-utilized last month?* * *Who was not working to capacity last month?* * *Which person logged the most hours where the job role was like "Developer"?* * *Show me how many hours per day each user is scheduled to work?* ### Risk and delivery Identify items and cycles at risk: * *Show me items where time logged > work estimate* * *Which cycles are at risk of not getting all work completed on time based upon cycle finish date?* * *Show me which boards have budget changes* * *What cycles do we have due this month?* ### Item and billing summaries Get item-level data: * *Show me top billable items* * *Show me item billable summary* * *Which resource that logged time has the highest base cost as per job role?* ### Performing actions The AI can also perform actions in your workspace — not just answer questions: * *Create me a new board "Fred"* * *Create new board called Dele with description "Hello World" and add three cycles* Actions performed by the AI follow the same access control rules as the rest of Ekso. You can only create or modify resources you have permission to manage. ## Context documents Documents in [Docs](/guide/concepts/docs) can be marked with the **AI Context** toggle. When enabled, the document content is included as context when the AI answers questions — giving it access to your organization's internal knowledge, runbooks, coding standards, or guidelines. This is useful for grounding the AI in your team's specific processes and terminology without needing to explain them in every conversation. Enable AI Context on documents that contain stable reference material — architecture decisions, onboarding guides, and standard operating procedures work well. Avoid enabling it on frequently changing drafts where stale content could produce inaccurate answers. ## Semantic search AI search finds content by meaning rather than exact keyword match. Enter a phrase and Ekso returns results ranked by semantic similarity across items and documents. This is useful when you know what you are looking for but not the exact wording used. Search results include the entity type (item or document), name, and a relevance score. ## Providers Ekso includes a built-in AI provider by default — AI features work without any additional configuration. If you prefer to use your own API key, you can configure one of two external providers under **Settings > AI**. ### OpenAI Connect directly to the OpenAI API. | Setting | Description | | ------- | ----------------------------------------------- | | Active | Enable or disable this provider | | Model | The OpenAI model to use (for example, `gpt-4o`) | | Key | Your OpenAI API key | ### Azure OpenAI Connect to an Azure-hosted OpenAI deployment. Use this if your organization requires data to stay within your Azure tenant or needs to comply with data residency policies. | Setting | Description | | -------- | --------------------------------------- | | Active | Enable or disable this provider | | Model | The deployed model name | | Endpoint | Your Azure OpenAI resource endpoint URL | | Key | Your Azure OpenAI API key | A provider is only active when all its required fields are filled in — toggling **Active** on without entering a key and model has no effect. AI features are rate-limited separately from the REST API. If you see rate limit errors, wait a moment before retrying. # Authentication Source: https://ekso.dev/guide/concepts/authentication Configure login methods — forms-based authentication with password policies, Microsoft Entra ID SSO, and two-factor verification. ## Overview Ekso supports two authentication methods: **forms-based login** (email and password) and **Microsoft Entra ID** (single sign-on). You can enable one or both at the same time. Administrators can always sign in with email and password, even if forms authentication is disabled — this prevents system lock-out. Authentication settings are configured under **Settings > Authentication**. ## Forms authentication Forms authentication lets users sign in with an email address and password. When active, Ekso enforces password policies and optionally requires two-factor authentication. ### Password policies Password policies control the complexity and lifecycle of user passwords. | Setting | Description | Default | | ------------------------ | ------------------------------------------------------------------------------ | ------------- | | Password change interval | Days before a password change is required. Set to 0 to disable forced changes. | 0 (never) | | Minimum length | Shortest allowed password | 8 characters | | Maximum length | Longest allowed password | 40 characters | | Require number | Password must contain at least one digit | On | | Require uppercase letter | Password must contain at least one uppercase letter | On | | Require lowercase letter | Password must contain at least one lowercase letter | On | | Require symbol | Password must contain at least one symbol (`!@#$%^&*()-=_+`) | Off | Ekso also prevents password reuse — users cannot set a new password that matches their previous password. ### Two-factor authentication When enabled, Ekso sends a verification code to the user's email address after they enter their password. The user must enter this code to complete sign-in. | Setting | Description | Default | | ------------------------- | ------------------------------------------------ | ------- | | Two-factor authentication | Enable or disable 2FA | Off | | 2FA code length | Number of digits in the verification code (6–10) | 6 | Two-factor codes are sent by email, not SMS or authenticator app. Make sure your mail configuration is working before enabling 2FA. ## Microsoft Entra ID Microsoft Entra ID (formerly Azure Active Directory) enables single sign-on for organizations on Microsoft 365. Users sign in with their Microsoft account instead of a separate Ekso password. To connect Entra ID, you need: | Setting | Description | | ----------------------- | ---------------------------------------------- | | Tenant ID | Your Microsoft Entra directory ID | | Application (client) ID | The app registration ID from your Entra tenant | Use Entra ID if your organization is on Microsoft 365. It removes the need for separate passwords and supports your organization's conditional access policies. ### Setting up Entra ID In the [Microsoft Entra admin center](https://entra.microsoft.com), go to **App registrations** → **+ New registration** (or select an existing app). Note the **Application (client) ID** and **Directory (tenant) ID** from the app's overview page. Open the app's **Authentication** blade → **+ Add a platform** → **Single-page application**, then add the following redirect URI: ``` https://ekso.acme.com/auth/entra/callback ``` Replace `ekso.acme.com` with your Ekso install's hostname — the `PublicUrl` you set during the `/startup` wizard (for example, `https://ekso.acme.com/auth/entra/callback`). Entra requires an exact match — scheme, host, and path must all line up — so add a separate entry for each install URL you sign in from (production, staging, dev, and so on). The **Single-page application** platform type configures Entra for the OAuth 2.0 authorization code flow with PKCE, which is what Ekso's frontend uses. Do not register the same URL under the **Web** platform — that flow expects a server-side client secret and will fail for a browser app. Click **API permissions** → **+ Add a permission** → **Microsoft Graph** → **Delegated permissions**. Add the following permissions: | Permission | Type | Description | Admin consent | | ---------------- | --------- | --------------------------------------------------- | ------------- | | `offline_access` | Delegated | Maintain access to data you have given it access to | No | | `openid` | Delegated | Sign users in | No | | `profile` | Delegated | View users' basic profile | No | | `User.Read` | Delegated | Sign in and read user profile | No | These are **Delegated** permissions — they act on behalf of a signed-in user during the SSO login flow. They do not require admin consent. If you also use Ekso's [mailbox connection](/guide/concepts/mailbox#azure-app-registration), the same app registration will need additional **Application** permissions for mail access. Go to **Settings > Authentication > Microsoft Entra** and enter the tenant ID and client ID. Toggle **Active** on. Users can now sign in with their Microsoft account. If you disable forms authentication while Entra ID is not properly configured, non-administrator users will be unable to sign in. Administrators can always sign in with email and password as a safety mechanism. ## How authentication methods work together You can run both authentication methods at the same time. When a user signs in: * If only **forms** is active, users sign in with email and password * If only **Entra** is active, users are redirected to Microsoft for sign-in (administrators can still use email and password) * If **both** are active, users can choose either method # Boards Source: https://ekso.dev/guide/concepts/boards Plan delivery with boards — set budgets, manage resources, approve containers, and organize work into cycles. ## Overview A board is a planning surface that groups work for delivery. Boards pull [items](/guide/concepts/items) from approved [containers](/guide/concepts/containers), assign approved resources, and organize work into time-boxed [cycles](/guide/concepts/cycles). Boards are commonly used to represent sprints, releases, quarterly plans, or project phases. Every board must have at least one cycle. ## Board settings Board configuration is split across five tabs under **Settings**: | Tab | Purpose | | --------- | ---------------------------------------------------------- | | Detail | Name, description, budget, and board-level toggles | | Access | Role-based permissions for user groups | | Resource | The pool of user groups whose members can be assigned work | | Container | Which containers can send items to the board | | Cycle | Create and manage the board's cycles | ## Detail The **Detail** tab captures the board's identity and high-level controls. * **Name** — the display name shown in board lists and navigation * **Description** — a rich-text summary of the board's purpose * **Budget** — hours allocated across all cycles (see [Budget tracking](#budget-tracking) below) * **Locked** — prevents any changes to the board or its cycles * **Archived** — hides the board from default views while preserving its data * **Deleted** — soft-deletes the board and all of its cycles Deleting a board also deletes all of its cycles. This cannot be undone from the UI. ## Budget tracking Every board has a budget expressed in hours. The budget is not edited directly — instead, you apply incremental changes that are recorded as an audit trail. ### Changing the budget Click **Edit** next to the budget value to open the budget change form. Each change captures: | Field | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------- | | Change hours | The number of hours to add or subtract | | Reason code | Why the budget changed — selected from your tenant's [budget reason codes](/guide/concepts/finance#budget-reason-codes) | | Initials | The initials of the person authorizing the change | The displayed budget is the running total of all change entries. You can review the full history to see how the budget evolved over the life of the board. ### Reason codes Every budget change requires a reason code — for example, scope increase, new requirement, or client request. Reason codes are configured at the tenant level under [Finance](/guide/concepts/finance#budget-reason-codes) and shared across all boards. The budget audit trail feeds into profitability and scope-tracking reports. Use descriptive reason codes so the history is meaningful months later. ## Access control The **Access** tab sets board-level permissions by assigning roles to user groups. Three roles control what users can do: | Role | Permission | | ------ | --------------------------------------------------------------------------------------- | | View | Can view the board and interact with its data | | Manage | Can perform any operation within the board — edit cycles, approve time, change settings | | Plan | Can send work to the board — assign items to cycles | Each role is granted to one or more user groups. For example, you might grant **View** to "Everyone", **Manage** to "Team Lead" and "Product Management", and **Plan** to "Developers" and "Support." New boards default to granting all three roles to the "Everyone" group. Restrict access after creation to lock down who can view, manage, and plan work. ## Approved resources The **Resource** tab defines which user groups form the board's resource pool. Only members of selected groups can be assigned to work on items within the board's [cycles](/guide/concepts/cycles). Resources in Ekso can be: * **Users** — specific people from the approved groups * **Job roles** — placeholder roles like "Senior Developer" or "QA Engineer" used for capacity planning before specific people are assigned The board manager selects which resources are available for each cycle from this approved pool. Different cycles can have different teams, which is useful when team composition changes between sprints or project phases. ## Approved containers The **Container** tab determines which [containers](/guide/concepts/containers) can send work to the board. Only items from approved containers can be planned into the board's cycles. This keeps the board focused on the work it is responsible for delivering. For example, a "Q3 Launch" board might approve the "Next-gen App" and "Support Desk" containers but not "Consulting Services." ## Cycles The **Cycle** tab lists all cycles belonging to the board, showing each cycle's name, description, budget, start date, and finish date. Cycles can be viewed as releases, phases, iterations, sprints, or milestones — the terminology is flexible. From this tab you can create new cycles and open existing ones for editing. For full details on cycle configuration and lifecycle, see [Cycles](/guide/concepts/cycles). ## Board vs. list Containers can be viewed as a flat list or organized into boards. The key differences: | | List view | Board view | | ------------------ | ----------------------- | -------------------------------------------------------------------- | | Cycles | Not required | At least one cycle required | | Resources | No resource allocation | Approved resource list | | Constraints | Basic field constraints | Additional constraints (job role, costs, SKU, time logs, containers) | | Financial insights | Not available | Cycle-level financial visibility | | Budget | Not available | Hours budget with change tracking | | Access control | Container-level | Board-level roles (View, Manage, Plan) | Use the list view for simple item tracking — help desk queues, backlogs, or lightweight task management. Use boards when you need planning, resource allocation, and financial tracking. # Clock Source: https://ekso.dev/guide/concepts/clock Enforce SLA targets with clocks — set working calendars, track elapsed time, detect breaches, and send notifications. ## Overview A clock enforces a service level agreement by tracking elapsed time on an [item](/guide/concepts/items). Every item is on a clock automatically from the moment it is created. Clocks track elapsed time against a target and flag items that are approaching or past their deadline. Each [process](/guide/concepts/processes) sets the expected time-to-close in hours using [rules](/guide/concepts/rules), and the clock calculates how much time remains. Different [containers](/guide/concepts/containers) can use different clock configurations. A support container can run 24x7, while a development container runs during working hours only. This is how enterprise customers drive differing service level agreements across the business. Clocks are separate from [time tracking](/guide/concepts/time-tracking). Time tracking records hours worked by people. Clocks track elapsed calendar time against SLA targets. ## Clock configuration You manage clocks under **Settings > Clock**. Each clock configuration defines working times, days, and non-working days, and can be scoped to specific containers or applied globally. | Property | Description | Default | | ---------------- | ----------------------------------------------------------- | ------------------ | | Name | Configuration name | "Default" | | Container | Which containers use this clock (select **All** for global) | All | | Timezone | Timezone for all clock calculations | UTC | | Start of day | When the working day begins (HH:MM, 24-hour) | 09:00 | | End of day | When the working day ends (HH:MM, 24-hour) | 17:00 | | Start of week | First working day | Monday | | End of week | Last working day | Friday | | Working clock | Whether the clock runs 24x7 or working hours only | Working hours only | | Non-working days | Holidays and closures in YYYY-MM-DD format | — | You can create multiple clock configurations. When processing an item, Ekso first looks for a clock that targets the item's container. If none is found, it falls back to the global clock (one with no container restriction). Use **Working hours only** when your SLA is measured in business hours (for example, "respond within 4 business hours"). Use **24x7** when your SLA runs continuously regardless of business hours (for example, a support desk with round-the-clock coverage). ## How clocks work Every item carries four clock fields: | Field | Description | | ------------ | ------------------------------------------------------------------------------- | | Clock start | When the clock started ticking (set automatically on item creation) | | Clock finish | When the item should be completed by (calculated from start + hours + calendar) | | Clock hours | The estimated time-to-close in hours (set via process rules) | | Clock delta | Hours remaining until the deadline | ### Start time The clock starts automatically when an item is created. Start time can only be changed through [rules](/guide/concepts/rules) — users cannot edit it directly. If an item is created outside of working hours, the clock aligns to the next working day start. ### Finish time The finish time is calculated by projecting the clock hours forward from the start time, counting only working hours: If the start time falls before the start of day, it moves forward to the start of day. If it falls after end of day, it moves to the next working day. The system steps through each day, subtracting available working hours from the total. Non-working days and holidays are skipped entirely. When all hours are allocated, the finish time is set. For 24x7 clocks, the full 24-hour day counts. For working-hours-only clocks, only the configured window counts. **Example:** If the start time is Monday 9:00 AM, the clock hours are 36, and the clock runs 9 AM–5 PM Monday–Friday, the finish time is the following Monday at 1:00 PM — 36 working hours spread across 4.5 business days. The finish time is always calculated. Users cannot edit it directly. To change the finish time, adjust the start time via a rule or change the clock hours. ### Delta The delta is the remaining hours until the deadline. Ekso calculates how many working hours have elapsed since the clock started, then subtracts from the total clock hours: **Delta = Clock hours − Working hours elapsed** A positive delta means time remains. A zero or negative delta means the SLA target has been reached or breached. ## Color coding The clock status is based on the **percentage of time consumed**, not the absolute delta. Ekso calculates what percentage of the total clock hours have elapsed, then compares against configurable thresholds. | Status | Default threshold | Meaning | | ------ | ----------------- | ------------------------------------------------------------ | | Green | 0–69% consumed | On time — most of the time budget remains | | Amber | 70–94% consumed | Warning — approaching the deadline | | Red | 95–100% consumed | Breach — the SLA target has been exceeded or nearly exceeded | The thresholds are configurable per clock configuration: | Threshold | Default | Description | | --------------- | ------- | ------------------------------------------------------------- | | Green threshold | 0% | Percentage at which status becomes green (always starts at 0) | | Amber threshold | 70% | Percentage at which status changes from green to amber | | Red threshold | 95% | Percentage at which status changes from amber to red | **Example:** An item has 10 clock hours. After 7 working hours have elapsed (70%), it turns amber. After 9.5 hours (95%), it turns red. ## Clock processing Clocks are recalculated automatically every 5 minutes by a background process. This process evaluates all open items across all tenants, applies the appropriate clock configuration, and updates the clock fields. Once an item is closed, its clock is frozen and no longer recalculated. All clock calculations respect the configured timezone. Clock start and finish times are stored in UTC internally but calculated in the clock's local timezone to ensure working hours align correctly with the business calendar. ## Breach notifications When a clock approaches or exceeds the SLA target, Ekso can send breach notification emails. These use conditional checks on the delta value, so you can configure different notifications at different thresholds — for example, send a warning when the clock turns amber and an escalation when it turns red. ## Clock and rules [Rules](/guide/concepts/rules) interact with clocks in two ways: * Rules can **modify the clock start time** — for example, to delay the SLA clock based on item priority or to reset it after a status change * Each process sets the **time-to-close** (clock hours) using rules, which determines when the finish time falls Rules are the only mechanism for adjusting clock behavior. This keeps SLA tracking consistent and auditable — all changes are driven by configured automation rather than manual overrides. # Constraints Source: https://ekso.dev/guide/concepts/constraints Enforce data integrity with include and exclude rules that control which field values are allowed on items. ## Overview Constraints enforce data integrity rules on items. They control what values are allowed in [fields](/guide/concepts/fields) based on conditions — think of them as "if this, then only that" rules for your data. Constraints are more powerful than simple cascading dropdowns because they work across any combination of list and picker fields. They ensure that your team can only enter valid data combinations, reducing errors and keeping your work items consistent. ## Creating a constraint Each constraint is built from a trigger (the "if" condition) and a constrained field (the "then" restriction). You configure these under **Settings > Constraint**. | Property | Description | | ----------------- | --------------------------------------------------------------------------------------------- | | Name | A descriptive name for the constraint | | Container | Which containers the constraint applies to (select **All** for tenant-wide) | | Trigger field | The field that triggers the constraint — when this field changes, the constraint is evaluated | | Trigger value | The specific value of the trigger field that activates the constraint | | Constrained field | The field whose available values are restricted by the trigger | | Constrained value | The values that are allowed or blocked in the constrained field | At the bottom of the form, two toggles control the constraint mode: * **Only selected values allowed** — include mode: the constrained field can *only* contain the selected values * **All values allowed except selected** — exclude mode: the constrained field can contain *any* value except the selected ones These toggles are mutually exclusive — enabling one disables the other. ## Include and exclude logic ### Include constraints An include constraint says: "if the trigger field has this value, then the constrained field can only have these specific values." You specify exactly which values are allowed. Any value not in the list is blocked. **Example:** If Priority is `High`, then Status can only be `In Progress`, `On Hold`, or `Closed`. ### Exclude constraints An exclude constraint says: "if the trigger field has this value, then the constrained field can have any value except these." You specify which values are blocked, and everything else remains available. **Example:** If Status is `Closed`, then Resolution cannot be `Unresolved`. Use include when the allowed set is small and well-defined. Use exclude when you only need to block a few specific values. ## Where constraints apply Constraints are scoped to [containers](/guide/concepts/containers). When you create a constraint, you select which containers it applies to — or choose **All** to apply it across every container in the tenant. Constraints apply to both **list** fields (like Status or Priority) and **picker** fields (like Cost Center or Job Role). This means you can control not just dropdown selections but also entity lookups. ## Constraints vs. process configuration Constraints are one of three mechanisms on a [process](/guide/concepts/processes) that control item data. Each serves a different purpose: | Mechanism | What it controls | Configured on | | ----------------------- | ------------------------------------------------------------- | ---------------------- | | **Constraints** | Which field *values* are allowed based on conditions | Settings > Constraint | | **Workflow** | Which *status transitions* are allowed, and who can make them | Process > Workflow tab | | **Field configuration** | Which *fields appear* on items, and who can view or edit them | Process > Field tab | If you need to restrict the values a user can select, create a constraint. If you need to control which statuses an item can move to, configure the workflow. If you need to show or hide a field entirely, use the process Field tab. ## Rules bypass constraints [Rules](/guide/concepts/rules) can set field values that would otherwise violate a constraint. This is by design. Rules act as a data manipulation escape hatch, allowing automated processes to make changes that user-driven actions cannot. For example, a rule might automatically set a status that a user could not select manually due to a constraint. This separation means constraints protect against user error while rules handle automated workflows without being blocked. # Containers Source: https://ekso.dev/guide/concepts/containers Organize work into containers with areas, labels, processes, saved filters, and role-based access control. ## Overview A container groups related items together. Think of it as a project, product, service line, or team workspace. Every [item](/guide/concepts/items) belongs to exactly one container, and containers are the primary organizational boundary for work management in Ekso. Containers define the structure for the work inside them: which [processes](/guide/concepts/processes) are available, how items are categorized with [areas](/guide/concepts/containers#areas) and [labels](/guide/concepts/containers#labels), and who has access. ## Container settings Container configuration is split across five tabs under **Settings**: | Tab | Purpose | | ------- | ---------------------------------------------------------------------- | | Detail | Name, code, description, label, owner, and container-level toggles | | Area | Sub-groupings for organizing items within the container | | Filter | Predefined filters shared with all container users | | Access | Role-based permissions for user groups | | Process | Item types available in the container, with field-level access control | ## Detail The **Detail** tab captures the container's identity and high-level controls. * **Name** — the display name shown in container lists and navigation * **Code** — a short identifier (up to four characters) used as the prefix for item keys. A container with the code `SVC` produces items like `SVC-1`, `SVC-2`, `SVC-3` * **Description** — a rich-text summary of the container's purpose * **Label** — a category tag displayed next to the container name in lists (for example, Service, Product, or Support) * **Owner** — the person responsible for the container * **Archived** — hides the container from default views and prevents changes while preserving its data * **Deleted** — removes the container from the system The **Sequence** button in the top-right corner renumbers all items in the container to remove gaps caused by deletions. ### Item numbering Items within a container are numbered sequentially using the container code as a prefix. For example, `SVC-1`, `SVC-2`, `SVC-3`. This gives every item a unique, human-readable identifier across the entire tenant. ## Areas Areas are sub-groupings within a container. You use them to organize items by feature, module, component, or team area — whatever subdivision makes sense for your work. Each area has a name and description. For example, a "Consulting Services" container might have areas for "Implementation" and "Maintenance." An item can belong to at most one area. Areas are scoped to a single container, so each container defines its own area structure independently. ## Labels Labels are flat tags defined globally and shared across all containers. Unlike areas, labels are not scoped to a single container. An item can have multiple labels. Use labels for concerns that span containers — for example, `security`, `documentation`, or `urgent`. Use areas for structure that is specific to a single container. ## Processes [Processes](/guide/concepts/processes) define item types within a container. A process controls the fields, features, workflow transitions, and permissions for items that follow it. When you create an item, you choose which process it follows — for example, Defect, New Feature, Enhancement, Change Request, or Task. Every new tenant comes with five default processes: | Process | Description | | -------------- | ------------------------------------ | | Defect | Defect triage and management | | New Feature | Introducing additional functionality | | Enhancement | Improving existing functionality | | Change Request | Change request management | | Task | General task management | All five share the same default workflow and field set. They are fully editable — you can rename them, adjust their fields, change their workflows, or delete the ones you do not need. A container can run multiple processes simultaneously. This means a single container can track defects, change requests, and tasks — each with their own fields, features, and workflow rules. You manage processes under **Settings > Process**. Each process can be enabled or disabled for the container, and each has an **Access** button for configuring field-level permissions. Each process has five configuration tabs — Fields, Features, Workflow, Clone, and Detail — that control everything about the item type. See [Process](/guide/concepts/processes) for full details. ### Field-level access control Each process in a container has a field-level permission matrix that controls which user groups can see, set, and change each field. Navigate to the container's **Process** tab and click the **Access** button next to the process you want to configure. See [Process — field-level access control](/guide/concepts/processes#field-level-access-control) for the full explanation. ## Saved filters Container admins can define saved filters that are shared with all users in the container. Saved filters provide quick access to common views like "Open", "My Work", "Owned By Me", or "Working." Each saved filter has: * **Name** — displayed in the filter dropdown on the container list view * **Active** — whether the filter appears in the dropdown for all users * **Filter conditions** — the query criteria that define the filter ### Filter conditions A filter is built from one or more condition groups joined by **OR** logic. Within each group, conditions are joined by **AND** logic. Each condition specifies: | Part | Description | | -------- | --------------------------------------------------------------------------------------------------------------- | | Field | The item field to evaluate (for example, Status, Severity, Resource) | | Operator | The comparison type (Equal, Not Equal, Contains, Starts With, Ends With, Less Than, Greater Than, and variants) | | Value | The target value to compare against | Filters also support: * **Sort** — choose a field to sort results by (for example, Name, Status) * **Include closed** — toggle whether closed items appear in results Users select saved filters from the dropdown on the container list view. They can also build ad-hoc filters using the same condition builder without saving them. Create filters for your team's most common views. A "My Work" filter using `Resource Equal Me` helps team members quickly find their assigned items. ## Access control The **Access** tab sets container-level permissions by assigning roles to user groups. Eight roles control what users can do: | Role | Permission | | ---------- | --------------------------------------------------------------------------- | | View | Can view and interact with the container | | Add | Can add new items to the container (subject to process access rules) | | Filter | Can filter items within the container (subject to process access rules) | | Manage | Can perform any operation within the container | | Change | Can change items within the container (subject to process access rules) | | Change Own | Can change own items within the container (subject to process access rules) | | Delete | Can delete items within the container (subject to process access rules) | | Delete Own | Can delete own items within the container (subject to process access rules) | Each role is granted to one or more user groups via the **Access** button next to each role. Container permissions work alongside process field-level permissions. A user needs both container-level access and process-level field access to interact with an item. ### Secure containers Ekso supports secure containers — containers where access is strictly enforced, even for Super Admins. If a Super Admin does not have explicit access to a secure container, they cannot see or manage it. This enables confidential workspaces for sensitive work like HR investigations, legal matters, or security incidents. A secure container is invisible to users without access, including Super Admins. Make sure at least one admin has access before restricting a container. # Cycles Source: https://ekso.dev/guide/concepts/cycles Run time-boxed iterations — plan items into cycles, assign resources, track status zones, approve time, and close sprints. ## Overview A cycle is a time-boxed iteration within a [board](/guide/concepts/boards). Think of it as a sprint, milestone, release, phase, or iteration — the terminology is flexible. Each cycle has a start date, end date, and its own budget in hours. Every board must have at least one cycle. Items are planned into cycles as cards, assigned to resources or job roles, and tracked through color-coded statuses. ## Cycle settings Each cycle is configured with the following properties: | Property | Description | | ------------ | ------------------------------------------------------ | | Name | Display name for the cycle | | Description | Rich-text summary of the cycle's goals | | Parent cycle | Optional parent for hierarchical organization | | Budget | Hours allocated to this cycle | | Start | Cycle start date | | Finish | Cycle end date | | Locked | Prevents changes to the cycle's data | | Closed | Marks the cycle as complete and finalizes time entries | | Deleted | Removes the cycle from the system | ### Parent/child cycles Cycles can be organized into parent/child hierarchies. A parent cycle acts as a grouping — for example, a "Research" parent might contain "Cycle 2" and "Research Extended" as children. Use parent cycles to model multi-phase work within a single board. For example, a board representing a product launch might have parent cycles for "Research", "Design", and "Build", each containing more granular child cycles. ### Budget Each cycle has its own budget in hours, separate from the [board budget](/guide/concepts/boards#budget-tracking). The cycle budget represents the hours allocated specifically to that iteration. The cycle view displays budget alongside actuals so you can track progress: * **Budget** — allocated hours for the cycle * **Estimated** — total estimated effort across all planned items * **Logged** — actual hours logged by resources ## Planning items into cycles You plan items into a cycle by assigning them to a board and cycle. Each planned item becomes a **card** on the cycle view. Items can only be planned from [containers approved](/guide/concepts/boards#approved-containers) on the board. ### Assigning to a resource You can assign a planned item to a specific person from the board's [approved resource list](/guide/concepts/boards#approved-resources). This records who is responsible for delivering the work in that cycle. ### Assigning to a job role Alternatively, you can assign an item to a [job role](/guide/concepts/finance#job-roles) placeholder — for example, "Senior Developer" or "QA Engineer." This is useful for capacity planning before specific people are assigned, letting you model the work needed without committing individuals. The cycle view provides **Resource** and **Job Role** filter dropdowns to help you focus on a specific person's or role's workload. ## Card colors Cards are color-coded by status for an at-a-glance view of where work stands: | Color | Status | | ----- | ------- | | Gray | Open | | Green | Working | | Red | Review | | Amber | Invalid | | Blue | Closed | ## Status zones Items within a cycle are grouped into four status zones: | Zone | Description | | ----------- | ------------------------------------------------------- | | In Progress | Items actively being worked on | | Open | Items ready for work but not yet started | | Closed | Items that have been completed | | Blocked | Items that cannot proceed due to dependencies or issues | Each zone displays the number of items it contains. Zones give board managers a quick summary of how work is distributed across the cycle. ### Time approval by zone Each zone has a **Time** button that opens a time entry review for all items in that zone. This lets board managers approve or reject time entries in bulk by work status rather than reviewing each item individually. The time review panel shows entries grouped by item, with each entry displaying: * **Duration** — hours and minutes logged * **Time type** — the category of work (Internal, Development, QA, R\&D, PMO, Sales, Marketing) * **Status** — whether the entry is approved or pending * **Description** — what the work involved * **Person and date** — who logged the time and when You can approve or reject individual entries using the thumbs up/down controls, or use **Approve** to approve all entries in the zone at once. Review time entries by zone before closing a cycle. The In Progress zone typically has the most entries to review, while Blocked items may have time logged against investigation work. ## Closing a cycle Closing a cycle is a significant, irreversible action that finalizes all time data for the iteration. ### What happens when you close 1. All [time entries](/guide/concepts/time-tracking) for items in the cycle are **automatically approved** — including any entries previously marked as rejected 2. No further time can be logged against items in the cycle 3. Approved time flows into billing and profitability reports The close action requires confirmation: you must type **YES** in a confirmation dialog to proceed. Closing a cycle auto-approves all time entries, including rejected ones. Review and resolve any rejected entries before closing if you do not want them approved. ### Locked vs. closed These are distinct states with different effects: | State | Effect | | ------ | ------------------------------------------------------------------------------------------------------------------------ | | Locked | Prevents changes to the cycle's items — no additions, edits, or removals. Time entries can still be logged and reviewed. | | Closed | Finalizes the cycle completely — all time entries are approved and no further time can be logged. | Lock a cycle when you want to freeze the scope but continue tracking time. Close a cycle when the iteration is finished and time data should be finalized for billing. ## Board and cycle hierarchy ```text theme={null} Board (e.g., "Dotcom Refresh") ├── Budget: 300 hours ├── Items: 63 ├── Containers: [Next-gen App, Support Desk] ├── Resources: [Developers, Support] │ ├── Research (parent cycle) │ ├── Budget: 10h | Estimated: 159h 5m | Logged: 342h 51m │ ├── Dates: 12/02/2026 – 22/02/2026 │ ├── Items: 26 │ │ │ ├── Cycle 2 (child) — 0 items │ └── Research Extended (child) — 0 items │ ├── Design — 20 items └── Build — 17 items ``` # Docs Source: https://ekso.dev/guide/concepts/docs Build a knowledge base with folders, user group visibility controls, and AI assistant integration for context-aware answers. ## Overview Docs is Ekso's built-in knowledge base. It provides a central place to create, organize, and share documents across your organization — from engineering runbooks and HR policies to sales playbooks and legal guidelines. Documents live inside folders, each folder representing a department or topic. You control who can see each folder and document using user group visibility, and you can optionally make document content available to the [AI assistant](/guide/concepts/ai) for context-aware answers. ## Folders Folders are the top-level organizer for documents. Each folder has a name, a color indicator, and optional visibility restrictions. | Field | Description | | ------------- | -------------------------------------------------------- | | Name | The folder name, shown in the left sidebar | | Color | A color badge displayed next to the folder name | | Restricted To | Which user groups can see the folder and its documents | | Archived | Hides the folder from default views and prevents changes | Common folder examples include Engineering, Legal, Marketing, Finance, Sales, HR, IT, Operations, and Support — but you can create folders for any team or topic. Use folder colors to visually distinguish departments at a glance. Each folder's color badge appears in the sidebar alongside the folder name. ## Documents A document is a single piece of content within a folder. Each document has metadata that controls its identity, visibility, and behavior. | Field | Description | | ------------- | ----------------------------------------------------------------------------------------- | | Name | The document title | | Author | The person who created the document | | Restricted To | Which user group can see this document | | Board | Optionally associate the document with a [board](/guide/concepts/boards) | | Container | Optionally associate the document with a [container](/guide/concepts/containers) | | AI Context | When enabled, the document content is available to the [AI assistant](/guide/concepts/ai) | | Archived | Prevents changes to the document while keeping it visible | | Deleted | Soft-deletes the document | ### Data context Documents can be linked to containers, boards, or items through data context associations. This connects knowledge to the work it relates to — for example, linking an "API v2 Migration Guide" to the "Next-gen App" container. A single document can have multiple context links, making it discoverable from different parts of the application. ### Visibility The **Restricted To** field controls which user group can see a document. When set, only members of the specified group can view the document in lists and open its content. Visibility works at two levels: * **Folder level** — restricts who can see the folder and browse its document list * **Document level** — restricts who can see an individual document, even within an accessible folder Document-level visibility is independent of folder-level visibility. A document restricted to "Developers" inside a folder visible to "Everyone" will only appear for developers. ## Rich text editor The document editor supports rich text formatting for creating structured, readable content. The toolbar provides: | Control | Description | | ----------------------- | -------------------------------------------------------- | | Headings | Six heading levels (H1 through H6) for content hierarchy | | Text formatting | Bold, italic, underline, strikethrough | | Superscript / subscript | For technical notation | | Alignment | Left, center, right, and justified text | | Lists | Ordered and unordered lists | | Code | Inline code and code blocks | | Links | Hyperlinks to external resources | Click the **Edit** button on a document to open the editor. Make your changes and click **Save** to persist them, or **Cancel** to discard. ## AI context When the **AI Context** toggle is enabled on a document, its content becomes available to the [AI assistant](/guide/concepts/ai) for retrieval-augmented generation. The AI can then reference the document when answering questions or performing searches. Use AI context for documents that contain organizational knowledge the AI should draw from — process documentation, coding standards, architecture decisions, onboarding guides, and similar reference material. The toggle appears in the document settings panel and is also indicated by an orange badge on the document preview: "Content will be used by AI for context." Enable AI context selectively for documents that contain stable, authoritative knowledge. Avoid enabling it on drafts or frequently changing documents where outdated content could lead to inaccurate AI responses. ## Access control Docs uses a three-level permission model assigned to user groups through the tenant's access control configuration. For application-level permission areas, see [Access control](/guide/concepts/access-control). | Role | Permission | | ------ | ---------------------------------------------------------------- | | View | Browse folders and read documents | | Add | Create and edit documents and their content | | Manage | Full control — create and manage folders, configure all settings | Deleting a folder or document is a soft delete — the record is marked as deleted but remains in the system. Only users with the appropriate role can perform deletions. ## Document settings Click the **Settings** button (gear icon) on a document to open the settings panel. From here you can configure: * **Name** — the document title displayed in lists and previews * **Restricted To** — which user group can see the document * **Board** — optionally link the document to a board * **Container** — optionally link the document to a container * **AI Context** — toggle whether the AI assistant can use the document content * **Archived** — mark as archived to hide from views and prevent changes * **Deleted** — remove the document from the system # Fields Source: https://ekso.dev/guide/concepts/fields Configure system fields and custom field types to capture structured data on items, controlled by processes. ## Overview Fields are the data attributes on an item. Every item carries a set of built-in system fields, and you can extend items with custom fields defined by your organization. Together, they capture everything you need to track about a piece of work. [Processes](/guide/concepts/processes) control which fields appear on items of a given type, the order they appear in, and whether they are required or optional. ## System fields System fields are built into every item. They cover the core attributes needed for work management, planning, time tracking, and auditing. ### Item fields These fields capture the primary data about a work item. | Field | Type | Description | | ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | Name | Text | The item title or summary | | Description | Rich text | Detailed description content | | Priority | List | Work priority level (Low, Medium, High) | | Severity | List | Impact level (Trivial, Minor, Major, Showstopper) | | Status | List | Current workflow status (Open, Unassigned, Accepted, Approved, Assigned, In Backlog, In Sprint, In Progress, On Hold, Ready, Pending, Closed, Blocked) | | Resolution | List | Final outcome (Unresolved, Resolved, Rejected, Ignored, Duplicate) | | Approved | Toggle | Simple approval capture | | Points | Integer | Work estimation — more points equals more effort | | Restricted to | Picker | Which user groups can see certain data | | Container | Picker | The container the item belongs to | | Area | Picker | Sub-grouping within the container (supports multi-select) | | Owned by | Picker | Who reported or owns the item | | Start date | Date | When work is due to start | | Due date | Date | When work is due to finish | | Percent complete | Percent | Completion percentage (0–100%) | | Work estimate | Time | Estimated effort (days and hours) | | Cost center | Picker | [Cost center](/guide/concepts/finance#cost-centers) for the item | | CRM | Picker | Linked [customer record](/guide/concepts/finance#crm-and-customers) | | SKU | Picker | Linked [stock unit](/guide/concepts/finance#skus-billable-time-codes) | | Job role | Picker | [Job role](/guide/concepts/finance#job-roles) associated with the item | | Process | Picker | The business process the item follows | | Resource | Picker | The person currently working on the item | ### Board and cycle fields These fields connect items to the planning system. | Field | Type | Description | | ----- | ------ | ----------------------------------------------------------- | | Board | Picker | The [board](/guide/concepts/boards) the item is planned on | | Cycle | Picker | The [cycle](/guide/concepts/cycles) the item is assigned to | ### Clock fields These fields track [SLA clock](/guide/concepts/clock) state for the item. | Field | Type | Description | | ------------ | ------- | -------------------------------------------- | | Clock start | Date | When the item was placed on the clock | | Clock finish | Date | When the item should be completed by | | Clock delta | Integer | Difference in hours between start and finish | | Clock hours | Integer | Expected duration of the clock in hours | | Clock status | List | Clock state (Green, Amber, Red) | ### Audit fields These fields are set automatically by the system. | Field | Type | Description | | ------------- | ------- | --------------------------------------------------- | | Sequence | Integer | Item sequence number within its container | | Create date | Date | When the item was created | | Created by | Picker | Who created the item | | Change date | Date | When the item was last modified | | Changed by | Picker | Who last modified the item | | Email GUID | Text | Unique ID for items created via email (system-only) | | Record source | Text | Where the item originated (user, email, API) | ### Feature fields These fields support specific Ekso features. | Field | Type | Description | | --------- | ---- | ------------------------------------------------------------------------------- | | Link type | List | Relationship type for linked items (Duplicate, Related) | | Time type | List | Category of time entry (Internal, Development, QA, R\&D, PMO, Sales, Marketing) | ### Configuring system list fields System fields that use the **List** type (Priority, Severity, Status, Resolution, Clock status) ship with default values, but you can configure them to match your workflow. For each value you can set: | Property | Description | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Active | Toggle a value on or off. Inactive values are hidden from new items but preserved on existing ones. | | Value | The display name shown to users | | Image | An icon or visual indicator for the value | | Metadata | Additional context for the value. For **Status** fields, this must be one of the four workflow verbs: `Open`, `Working`, `Closed`, or `Blocked` — determining which lifecycle phase the status belongs to. Other list fields can use metadata freely. | | Default value | Which value is pre-selected when a new item is created | Retire obsolete list values by toggling them inactive rather than deleting and re-creating them. This preserves referential integrity on existing items. Inactive values that are no longer in use on any item are deleted automatically. ## Default field values System list fields ship with default values that cover common workflows. Admins can add, rename, or remove values to match their organization's needs. ### Priority Default: **Medium** | Value | Icon | | ------ | ---------- | | Low | Priority 3 | | Medium | Priority 2 | | High | Priority 1 | ### Severity Default: **Minor** | Value | Icon | | ----------- | ------------- | | Trivial | Signal Fair | | Minor | Signal Good | | Major | Signal Strong | | Showstopper | Signal Best | ### Status Default: **Unassigned** Statuses are grouped into workflow phases that determine how items behave in boards, cycles, and reports. | Value | Workflow phase | | ----------- | -------------- | | Open | Open | | Unassigned | Open | | Accepted | Open | | Approved | Open | | In Backlog | Open | | In Sprint | Open | | Ready | Open | | Pending | Open | | Assigned | Working | | In Progress | Working | | On Hold | Working | | Closed | Closed | | Blocked | Blocked | ### Resolution Default: **Resolved** | Value | | ---------- | | Unresolved | | Resolved | | Rejected | | Ignored | | Duplicate | ### Link types Default: **Related** | Value | | --------- | | Duplicate | | Related | ### Billable time types | Value | | ----------- | | Internal | | Development | | QA | | R\&D | | PMO | | Sales | | Marketing | All list field values are fully editable. Add values that match your workflow, retire values you do not use, and set the default to the value your team selects most often. ## Custom field types Beyond system fields, you can create custom fields to capture data specific to your organization. Ekso supports nine custom field types: | Type | What it stores | Example use | | ------- | --------------------------------------- | ------------------------------------ | | Text | Free-form text | Customer reference, external ID | | Integer | Whole numbers | Story points, item count | | Decimal | Numbers with fractional precision | Cost estimate, hourly rate | | Percent | Percentage values | Completion percentage, discount | | Date | Calendar dates | Target date, review date | | Time | Duration values | Estimated hours, time spent | | Toggle | On/off boolean | Requires approval, is external | | List | Single selection from predefined values | Environment, category, region | | Picker | Lookup reference to related data | Related user, container, cost center | ### Custom field properties When you create a custom field, you configure the following properties: | Property | Description | | -------------- | ------------------------------------------------------------------------------------------------ | | Field | The unique field name. Reserved field names (used by system fields) are not allowed. | | Prompt | Maps to a localization string in language files — this is the label users see. | | Type | The data collection method (one of the nine types above). Set at creation and cannot be changed. | | Notes | A description or help text for the field. | | Content type | How the input is rendered (e.g., plain text, rich text). Applies to text fields. | | Maximum length | Character limit for text input. Set to zero for no limit. | | Default value | The value pre-filled when a new item is created. | Custom list fields have the same value configuration as system list fields — you can add values, assign images, set a default, and retire obsolete entries. ## How processes use fields A [process](/guide/concepts/processes) determines which fields appear on items of that type, their display order, and whether each field is required or optional. The same field can behave differently across processes — required in one, optional in another, or absent entirely. Some fields are always present on every process and cannot be removed: the audit fields (CreateDate, ChangeDate, CreateBy, ChangeBy, RecordSource, EmailGuid) plus Name and Status. All other fields can be added, removed, reordered, and marked as required through the process **Field** tab. To change which fields appear on an item type, update the process configuration under **Settings > Process > Field**. Processes are the single place that controls field visibility and requirements. [Constraints](/guide/concepts/constraints) can further control what values are allowed in fields based on conditions, adding data integrity rules on top of the process configuration. # Finance Source: https://ekso.dev/guide/concepts/finance Set up financial tracking — configure job roles, cost centers, SKUs, CRM records, and budget reason codes for profitability analysis. ## Overview Ekso includes a full financial model that connects work management to cost tracking, billing, and profitability analysis. Every [time entry](/guide/concepts/time-tracking) carries financial metadata — who did the work (job role and cost), what it was for (SKU and rate), and who it was charged to (customer/CRM). This financial layer ties together [boards](/guide/concepts/boards), [cycles](/guide/concepts/cycles), and [time tracking](/guide/concepts/time-tracking) to give you visibility into project economics — from individual time entries up to organization-wide profitability. Whether you are working for an external customer or on internal projects, costs and margins still apply. You manage all finance settings under **Settings > Finance**. ## Job roles A job role represents a professional function within your organization — for example, Backend Developer, Business Analyst, Project Manager, or Customer Service Agent. | Property | Description | | ------------------ | ------------------------------------------------------------------ | | Name | Display name (e.g., "Backend Developer") | | Code | Short identifier (e.g., `BKDEV`) | | Hourly base cost | What you pay for this role per hour — your internal cost | | Hourly charge rate | What you charge clients for this role per hour — your revenue rate | | Deleted | Remove from the system | Job roles are assigned to users. When a user logs time, their job role determines the cost and charge rate used in profitability calculations. The gap between base cost and charge rate is your margin per hour. ### Default job roles Every new tenant is installed with 34 default job roles covering common professional functions. Each has a pre-set hourly base cost and charge rate that you can adjust to match your organization's rates. | Code | Name | Base cost | Charge rate | | ----- | ---------------------------- | --------- | ----------- | | AI | ML / AI Engineer | 700 | 1,400 | | ARCH | Software Architect | 600 | 1,200 | | ASE | Application Support Engineer | 300 | 600 | | BA | Business Analyst | 400 | 800 | | BIA | BI Analyst | 450 | 900 | | BKDEV | Backend Developer | 450 | 550 | | CEO | CEO | 1,000 | 4,000 | | CFO | CFO | 1,000 | 4,000 | | CIO | CIO | 1,000 | 4,000 | | COO | COO | 1,000 | 4,000 | | CSA | Customer Service Agent | 200 | 300 | | CSM | Customer Service Manager | 300 | 400 | | CTO | CTO | 1,000 | 3,500 | | DA | Data Analyst | 400 | 600 | | DE | DevOps Engineer | 350 | 400 | | DM | Delivery Manager | 350 | 400 | | DS | Data Scientist | 600 | 1,200 | | FA | Financial Analyst | 300 | 300 | | FC | Financial Controller | 500 | 500 | | FD | Frontend Developer | 470 | 900 | | HPMO | Head of PMO | 700 | 1,400 | | HR | Head of Recruitment | 600 | 600 | | MOB | Mobile Developer | 550 | 550 | | PM | Project Manager | 575 | 1,000 | | PROD | Product Owner | 575 | 1,000 | | QA | QA/Test Engineer | 375 | 1,000 | | SA | Systems Administrator | 275 | 400 | | SCRUM | Scrum Master | 450 | 800 | | SE | Security Engineer | 395 | 700 | | SOFT | Software Engineer | 395 | 700 | | SVP | Senior VP | 800 | 2,000 | | TL | Technical Lead | 600 | 2,000 | | UX | UX Designer | 555 | 1,400 | | VP | VP | 775 | 1,400 | Rates are in your tenant's configured currency. All default roles are fully editable — add new roles, remove ones you do not need, and adjust rates to reflect your organization's actual costs and billing rates. Job roles also serve as placeholders when [planning items into cycles](/guide/concepts/cycles#assigning-to-a-job-role) — you can assign work to a role before deciding which specific person will do it. ## Cost centers A cost center is an organizational grouping for tracking where costs are incurred — for example, a department, team, or business unit. | Property | Description | | ------------ | --------------------------------------------------------------------- | | Name | Display name (e.g., "Executive Leadership / C-Suite") | | Code | Short identifier (e.g., `DTR`) | | Finance code | External code for integration with accounting systems (e.g., `INSKJ`) | | Deleted | Remove from the system | Cost centers are assigned to users alongside their job role. When calculating profitability, Ekso uses the cost center from the user who logged the time, allowing you to see how costs are distributed across your organization. ## SKUs (billable time codes) A SKU (stock-keeping unit) represents a billable product or service that you offer. SKUs link the work being performed to financial analysis — each has a unit price for revenue calculations. | Property | Description | | ------------ | ----------------------------------------------------------------------- | | Name | Display name (e.g., "Service 1") | | Code | Short identifier (e.g., `S1`) | | Finance code | External code for integration with accounting systems (e.g., `TS-SRV1`) | | Unit price | Rate per hour for this type of work (e.g., `750.22`) | | Deleted | Remove from the system | When a user logs billable [time](/guide/concepts/time-tracking#billable-time), they select a SKU. The SKU's unit price is used to calculate revenue in profitability reports. SKUs can also be linked to [CRM customers](#crm-and-customers) to define which products or services are available for each client. ## Non-billable codes Non-billable codes categorize time that is not chargeable to a client. Each code represents a category of internal work — for example, meetings, training, HR activities, or vacation. | Property | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------ | | Code | Short identifier (e.g., `HR`) | | Name | Display name (e.g., "HR") | | Description | Rich text description of the category (e.g., "Conducting job interviews, appraisals and recruitment related activities") | | Active | Whether this code is available for use | | Groups | Which user groups can use this non-billable time entry category | | Deleted | Remove from the system | The **Groups** setting controls which users see a non-billable code when logging time. Select **Everyone** to make a code available to all users, or restrict it to specific teams. For example, an "HR" non-billable code might be limited to the HR team, while "Meetings" is available to everyone. Non-billable codes appear in [time tracking reports](/guide/concepts/time-tracking#reporting), helping you understand where non-chargeable time goes and identify opportunities to improve billable utilization. ## CRM and customers CRM records represent your customers, partners, and external vendors. Each record captures the customer's identity and contact information, and connects them to the work you do for them. | Property | Description | | --------------------- | -------------------------------------------------------- | | Name | Customer or company name (e.g., "Example Inc") | | Code | Short identifier (e.g., `EXPL`) | | Domain | Comma-delimited email domain names (e.g., `example.org`) | | Address | Street address | | Zipcode / postal code | Postal code | | Country | Country (selected from dropdown) | | SKU | Which products and services are linked to this customer | | Deleted | Remove from the system | ### Customer domains CRM records can have one or more email domains. These domains are used for automatic matching — when an email arrives through the [mailbox integration](/guide/concepts/mailbox), the sender's domain can be matched to a CRM record to automatically associate the resulting ticket with the right customer. ### Customer contacts Each CRM record can have multiple contacts, each with a name, email address, and phone number. Contacts represent the people at the customer organization you interact with. ### Linked SKUs CRM records can be linked to specific SKUs, defining which products or services are available for each customer. When logging billable time against items associated with that customer, the linked SKUs control which time codes are relevant. This ensures that your team only selects SKUs that are part of the customer's agreement. ## Budget reason codes A budget reason code explains why a [board's budget](/guide/concepts/boards#budget-tracking) was changed. Reason codes document changes to hours, costs, points, and other budget metrics, creating an auditable history. | Property | Description | | -------- | --------------------------------------------- | | Name | Display name (e.g., "Scope increase") | | Code | Short identifier (e.g., `INC`) | | Active | Whether this reason code is available for use | | Deleted | Remove from the system | Reason codes are configured at the tenant level and shared across all boards. Every budget change requires selecting a reason code. ### Default reason codes Every new tenant is installed with seven default reason codes: | Code | Name | | ------ | --------------------- | | INIT | Initial | | INC | Scope increase | | DEC | Scope decrease | | CANCEL | Work cancellation | | ADD | Additional work order | | DELAY | Third party delay | | Redo | Re-work | All default reason codes are editable — add codes that match your change management vocabulary, or remove the ones you do not use. Use reason codes consistently across boards. Structured reason codes give you reporting on *why* budgets change over time, not just *that* they changed. ## How finance connects to work The financial model connects to work management at every level: * **Job roles** attach to users, providing cost and charge rates for every time entry * **Cost centers** attach to users, grouping costs by organizational unit for reporting * **SKUs** attach to billable time entries, calculating revenue from logged hours * **CRM records** link items to customers, connecting work to client relationships * **Non-billable codes** categorize internal time, restricted by user group * **Budget reason codes** document why board budgets change, creating an audit trail ### Profitability Ekso calculates profitability from the financial metadata on time entries: * **Estimated cost** = time logged x job role hourly base cost * **Estimated revenue** = time logged x job role hourly charge rate * **Gross margin** = (revenue - cost) / revenue Profitability reports can be grouped and filtered by container, board, cycle, SKU, job role, or cost center — giving you visibility from a single item up to the entire organization. # Insights Source: https://ekso.dev/guide/concepts/insights Analyze profitability, time distribution, per-user summaries, item financials, and risk with built-in reports. ## Overview Insights is Ekso's built-in reporting module. It provides financial and operational reports across five tabs: Profitability, Time, User, Item, and Ask. All reports share a date range picker and can be exported. Reports draw on [time tracking](/guide/concepts/time-tracking) data combined with [job role](/guide/concepts/finance#job-roles) rates to calculate cost, revenue, and margin figures. The date range defaults to the last three months if not set. ## Profitability The Profitability tab shows financial performance grouped by a dimension you choose from the dropdown: | Dimension | Groups by | | ----------- | -------------------------------------------------------------------------- | | Job Role | The [job role](/guide/concepts/finance#job-roles) assigned to each user | | Container | The [container](/guide/concepts/containers) each item belongs to | | Board | The [board](/guide/concepts/boards) items are planned on | | Cycle | The [cycle](/guide/concepts/cycles) within a board | | SKU | The [billable time code](/guide/concepts/finance#skus-billable-time-codes) | | Cost Center | The [cost center](/guide/concepts/finance#cost-centers) | | Item | Individual work [items](/guide/concepts/items) | Each row in the report displays: | Column | Description | | ------------------ | ---------------------------------------------------------- | | Total Hours | All hours logged within the date range | | Billable Hours | Hours logged against billable time types | | Non-Billable Hours | Hours logged against non-billable categories | | Billable % | Billable hours as a percentage of total hours | | Estimated Cost | Billable hours multiplied by the job role's base cost rate | | Estimated Revenue | Billable hours multiplied by the job role's charge rate | | Gross Margin % | (Revenue minus Cost) divided by Revenue | ### Profitability drilldown Click any row to open a drilldown panel showing the individual time entries behind that group. The drilldown displays the total time logged and a list of entries with: * **Resource** — the person who logged the time * **Item** — the work item the time was logged against * **Time** — the duration of each entry Use profitability grouped by Job Role to compare cost-efficiency across roles. High gross margin percentages indicate roles where charge rates are well above cost rates. Low margins may signal roles that need rate adjustments. ## Time The Time tab surfaces risk by comparing work estimates against actual time logged. It shows items where **time logged exceeds the work estimate** — an early indicator of scope creep or underestimation. Each row displays: | Column | Description | | ------------- | ------------------------------------------ | | Item | The item name | | Work Estimate | The original estimate in hours and minutes | | Time Logged | The actual time logged so far | | Board | The board the item is planned on | | Cycle | The cycle within that board | Only items in open cycles with a status other than Closed are included. The report is limited to the top 100 at-risk items, sorted by container, board, and cycle. Items appearing on this report have already exceeded their estimates. Review them promptly to decide whether to increase the estimate, reassign resources, or adjust scope. ## User The User tab shows a per-person summary of time data across the date range. Each row displays: | Column | Description | | ------------------ | ------------------------------------------------------------ | | Name | The user's name | | Job Role | Their assigned [job role](/guide/concepts/finance#job-roles) | | Total Hours | All hours logged | | Billable Hours | Hours on billable work | | Non-Billable Hours | Hours on non-billable categories | | Billable % | Billable hours as a percentage of total | | Non-Billable % | Non-billable hours as a percentage of total | ### User drilldown Click any user to open a detail panel showing their total time logged and a chronological list of every time entry. Each entry shows: * **Date** — when the time was logged * **Billable** — whether the entry is billable (Yes or No) * **Item / Time Type** — the item name for billable entries, or the non-billable category name * **Time** — the duration The drilldown includes both billable and non-billable entries, giving a complete picture of how the person spent their time during the selected period. Use the User report to monitor utilization. A low Billable % may indicate a person is spending too much time on non-chargeable activities, or it may reflect a role that is not expected to be client-facing. ## Item The Item tab shows per-item financial data, ranking items by billable hours. This report surfaces which items are consuming the most effort and generating the most revenue. Each row displays: | Column | Description | | -------------- | --------------------------------------------------------------- | | Item | The item key and name (for example, HELP-76) | | Container | The [container](/guide/concepts/containers) the item belongs to | | Board | The [board](/guide/concepts/boards) the item is planned on | | Billable Hours | Total billable hours logged | | Cost | Billable hours multiplied by the job role's base cost rate | | Revenue | Billable hours multiplied by the job role's charge rate | | Gross Margin | Revenue minus Cost | Items are sorted by billable hours in descending order, showing the most effort-intensive items first. Only items with billable hours greater than zero are included. ## Ask The Ask tab provides access to the [AI assistant](/guide/concepts/ai) from within Insights. Ask natural-language questions about your workspace and get structured answers — tables, financial summaries, and utilization data. The AI can also perform actions like creating boards or cycles. Questions can reference date ranges, container names, board names, and other filters in plain language. For example, "Show me most profitable items" returns a ranked table with item keys, containers, boards, billable hours, cost, revenue, and profit. Conversations are saved per user and can be continued later. See [AI](/guide/concepts/ai) for the full list of question categories, sample questions, and provider configuration. ## Date range The Profitability, Time, User, and Item tabs share a date range picker in the top-right corner. Select a start and end date to filter the data. If no range is set, reports default to the last three months. The date range applies to the time entries included in the calculations — only entries with a logged date within the range are counted toward hours, cost, and revenue figures. The Ask tab does not use the date range picker — instead, specify date ranges directly in your question (for example, "Show me revenue for November and December"). ## Exporting Each report includes an export button (download icon) that lets you download the data in CSV or Excel format. Use this to share reports with stakeholders or import the data into spreadsheets and external tools for further analysis. ## How costs are calculated Insights calculates cost and revenue using [job role](/guide/concepts/finance#job-roles) hourly rates: * **Cost** = billable hours multiplied by the job role's hourly base cost * **Revenue** = billable hours multiplied by the job role's hourly charge rate * **Gross Margin** = revenue minus cost * **Gross Margin %** = (revenue minus cost) divided by revenue, as a percentage These calculations depend on job roles having their base cost and charge rates configured. See [Finance](/guide/concepts/finance) for how to set up job roles and rates. Cost and revenue figures are estimates based on the rates configured on each job role at the time the report is run. If rates change, the reported figures will reflect the current rates, not the rates in effect when the time was originally logged. # Items Source: https://ekso.dev/guide/concepts/items Track work with items — fields, annotations, attachments, links, dependencies, time entries, and cycle planning. ## Overview An item is the core unit of work in Ekso. Items represent tickets, tasks, bugs, features, or any other trackable piece of work. Every item belongs to exactly one [container](/guide/concepts/containers) and follows a [process](/guide/concepts/processes) that defines its fields, features, and workflow. Items carry standard [fields](/guide/concepts/fields) like name, status, priority, and severity, plus any custom fields defined on their process. [Constraints](/guide/concepts/constraints) and [rules](/guide/concepts/rules) can further control and automate how field values behave. ## Item keys Each item has a unique key composed of its container code and a sequential number — for example, `SVC-23`. Keys are stable identifiers you can use to reference items across the application. The container code comes from the [container](/guide/concepts/containers#item-numbering) the item belongs to. ## Item detail When you open an item, its detail view displays the fields configured by its process. A typical item shows: | Field | Description | | ------------- | ------------------------------------------------------------------------ | | Process | The item type (for example, Task, Defect, New Feature) | | Status | Current workflow stage | | Created | Creation date and who created it | | Changed | Last modification date | | Area | Sub-grouping within the container | | Priority | Urgency level | | Severity | Impact level | | Resolution | Reason for closing (for example, Resolved, Rejected, Duplicate, Ignored) | | Owned By | The person responsible for the item | | Progress | Percentage complete (slider) | | Work Estimate | Estimated effort in hours and minutes | | SKU | Billable work category | | Clock | SLA countdown timer with color-coded status | | Restricted To | Which user group can see this item | Not all fields appear on every item. The process controls which fields are visible, and [field-level permissions](/guide/concepts/processes#field-level-access-control) control who can see, set, and change each field. Items created through the [ticketing](/guide/concepts/ticketing) pipeline display an email metadata bar beneath the field summary, showing the sender's email address and the receiving mailbox address. ## Item lifecycle Each item moves through a status workflow defined by its process. The lifecycle has three broad phases: The item is created and enters its initial status. It is now visible and ready to be triaged, prioritized, or assigned. Someone begins actively working on the item. A working timestamp is recorded, marking when effort started. The item is resolved. A resolution value is set (for example, Resolved, Rejected, Duplicate, Ignored) along with a closed timestamp. Status transitions are governed by the process workflow. Not every status can transition to every other status — the process defines which moves are valid and which user groups can make each transition. ## Item tabs Below the field summary, items organize related data into tabs. Which tabs appear depends on the features enabled on the item's process. ### Description The description tab displays the item's rich-text content. This is the primary space for detailed requirements, acceptance criteria, or context about the work. ### Attachments The attachment tab lets you upload files to the item. Use attachments for screenshots, documents, logs, or any supporting material. ### Annotations Annotations are threaded entries on an item used for commentary, updates, and collaboration. Unlike a simple comment field, annotations support three content types: | Type | Purpose | | ----- | ----------------------------------------- | | Text | Rich-text commentary with full formatting | | Embed | Embedded content from external sources | | Code | Code snippets and technical content | Each annotation records who created it and when. Annotations can come from three sources: | Source | Description | | ------ | -------------------------------------------------------------------------------------- | | User | Added manually by a team member | | Email | Created automatically from an inbound email via [ticketing](/guide/concepts/ticketing) | | System | Generated by the API or system automation | #### Annotation visibility Each annotation has its own **Restricted To** setting, independent of the item's visibility. This means you can have a public item with private annotations — for example, internal notes on a customer-facing ticket that only the support team can see. Use annotation visibility to keep internal discussion private on items that are visible to a wider audience. Set the annotation's **Restricted To** field to limit who can see it. ### Ticket The Ticket tab appears on items created through the [mailbox ticketing](/guide/concepts/ticketing) pipeline. It shows the email conversation history — both inbound messages from the sender and outbound replies from agents. Each entry displays the message content, sender, and timestamp. This tab is separate from annotations. Annotations capture internal team commentary, while the Ticket tab tracks the external email conversation. Use the Ticket tab to review the full email thread with the customer. Use annotations for internal notes that should not be sent externally. ### Time The time tab shows [time entries](/guide/concepts/time-tracking) logged against the item. Each entry displays: | Field | Description | | --------------- | -------------------------------------------------------------------------------------- | | Duration | Hours and minutes logged | | Time type | Category of work (for example, Internal, Development, QA, R\&D, PMO, Sales, Marketing) | | Status | Pending, Approved, or Rejected | | Description | What the work involved | | Person and date | Who logged the time and when | Time entries are created by team members and approved through the [cycle time approval](/guide/concepts/cycles#time-approval-by-zone) process. ### Links Links create cross-references between items. You search for an item by keyword or number and select a link type to describe the relationship — for example, Duplicate, Related, Blocks, or Blocked By. Links work across containers. An item in `SVC` can link to an item in `FEN`, making it easy to track related work across different projects or teams. ### Dependencies Dependencies create parent-child relationships between items, forming a hierarchical tree. A parent item represents a larger piece of work, and its children represent the subtasks or prerequisites needed to complete it. You add dependencies by searching for items by keyword or number. Dependencies work across containers — a parent in one container can have children in another. #### Dependency tree rules Parent items (items with children) behave differently from leaf items: | Rule | Description | | ----------------- | ------------------------------------------------------------------------------------------- | | Max children | A parent item can have up to **99** direct children | | No time logging | Time cannot be logged directly against a parent item — log time on the child items instead | | No board planning | Parent items cannot be placed on boards or assigned to cycles — only leaf items are planned | | Auto-close | When all children of a parent item are closed, the parent closes automatically | These rules ensure that work tracking and planning happen at the leaf level, while parent items serve as rollup summaries. If you need to track time or plan work against a high-level item, keep it as a standalone item without children. Once you add a child, the parent becomes a rollup container and loses the ability to carry time entries or board assignments. ## Visibility The **Restricted To** field controls which user group can see an item. By default, items are visible to everyone with container access. Setting a specific group restricts visibility to members of that group only. Visibility applies at two levels: * **Item level** — the Restricted To field on the item controls who can see it in lists and open its detail view * **Annotation level** — each annotation has its own Restricted To setting, allowing private commentary on otherwise-visible items This two-level model lets you keep items visible to a broad audience while restricting sensitive discussion to specific teams. ## Filtering items The container list view shows all items in a container. You filter items using saved filters or ad-hoc conditions. ### Saved filters Saved filters are predefined by container admins and appear in the filter dropdown. Common examples include "Open", "My Work", "Closed", "Open or Working", and "Owned By Me." See [Saved filters](/guide/concepts/containers#saved-filters) for how they are configured. ### Ad-hoc filters You can also build a filter on the fly using the same condition builder. Choose a field, operator, and value, then add more conditions or OR groups as needed. Ad-hoc filters are not saved — they apply only to your current session. ## Planning items onto boards The **Plan** button on an item assigns it to a [board](/guide/concepts/boards) and [cycle](/guide/concepts/cycles). Once planned, the item shows a "Planned" badge with a breadcrumb showing the board, cycle, and assigned resource — for example, "Dotcom Refresh > Design > Sarah Chen." Items can only be planned onto boards that have [approved](/guide/concepts/boards#approved-containers) the item's container. The assigned resource must come from the board's [approved resource pool](/guide/concepts/boards#approved-resources). For details on how items are tracked within cycles, see [Cycles](/guide/concepts/cycles). ## Watch, pin, and follow You can track items you care about without being assigned to them. Ekso supports two watch types: | Type | What it does | | ------ | --------------------------------------------------------------------------- | | Pin | Bookmarks the item for quick access from your personal list | | Follow | Subscribes you to updates — you receive notifications when the item changes | ### Watcher types Watches can be created for different recipient types: | Watcher type | Description | | ------------ | ------------------------------------------------------------------ | | User | An individual Ekso user | | Email | An external email address (for stakeholders without Ekso accounts) | | User group | An entire user group — all members receive updates | See the [API reference](/api-reference/introduction) for endpoint details on watches. Pin items you need to check regularly. Follow items where you want to be notified of changes without being the assigned resource. # Mailbox connections Source: https://ekso.dev/guide/concepts/mailbox Connect IMAP or Microsoft 365 mailboxes to Ekso for email-based ticketing. ## Overview A mailbox connection tells Ekso how to access an external email account. Once connected, you can set up [ticketing](/guide/concepts/ticketing) to turn inbound emails into work items, reply from within Ekso, and maintain threaded conversations. You manage mailbox connections under **Settings > Mailbox**. Ekso supports two connection types: IMAP and Microsoft 365. ### IMAP Connect to any IMAP-compatible mail server — Google Workspace, Fastmail, or any standard email provider. | Property | Description | Default | | ---------------- | --------------------------------------------------- | ------- | | Name | Display name for this mailbox connection | — | | Connection type | Select **Imap** | — | | Server | IMAP server address (e.g., `imap.gmail.com`) | — | | Port | IMAP port | 993 | | Username | Mailbox account username | — | | Password | Mailbox account password | — | | SMTP server | Server for sending replies (e.g., `smtp.gmail.com`) | — | | SMTP server port | SMTP port | 587 | | Inbox folder | Which folder to watch for new emails | Inbox | IMAP connections track the last processed email by its UID (a unique message identifier assigned by the mail server). Each processing cycle queries for unseen messages or messages with a UID greater than the last processed one. This means IMAP picks up exactly where it left off, message by message. ### Microsoft 365 Connect to Microsoft 365 mailboxes using OAuth authentication through Microsoft Graph. | Property | Description | | ------------------------- | ------------------------------------------------------------------- | | Name | Display name for this mailbox connection | | Connection type | Select **Microsoft** | | Email address | Email address of the mailbox to be used | | OAuth tenant ID | Your Azure AD tenant ID (found in admin.microsoft.com) | | OAuth client ID | The application client ID from your Azure app registration | | OAuth client secret value | The secret **value** (not the secret ID) from your app registration | | Inbox folder | Which folder to watch for new emails (default: Inbox) | #### Azure app registration Before you can connect a Microsoft 365 mailbox, you need an app registration in Microsoft Entra with the correct Microsoft Graph permissions. Go to [entra.microsoft.com](https://entra.microsoft.com) → **App registrations** → select your app (or create a new one). Click **API permissions** → **+ Add a permission**. Select **Microsoft Graph** → **Application permissions**. Search for and add the following permissions: | Permission | Type | Description | Admin consent | | ----------------------------- | ----------- | ----------------------------------------- | ------------- | | `Mail.Read` | Application | Read mail in all mailboxes | Yes | | `Mail.ReadBasic` | Application | Read basic mail in all mailboxes | Yes | | `Mail.ReadBasic.All` | Application | Read basic mail in all mailboxes | Yes | | `Mail.ReadWrite` | Application | Read and write mail in all mailboxes | Yes | | `Mail.Send` | Application | Send mail as any user | Yes | | `MailboxFolder.ReadWrite.All` | Application | Read and write all users' mailbox folders | Yes | Click **Grant admin consent for \[your organization]** and confirm. All six permissions require admin consent. These are **Application** permissions, not Delegated. This means Ekso accesses the mailbox directly without a signed-in user — which is why admin consent is required for all six permissions. If you already have an app registration for [Entra SSO](/guide/concepts/authentication#setting-up-entra-id), you can add these permissions to the same registration. When creating a client secret under **Certificates & secrets**, copy the secret **value** — not the secret ID. The value is only shown once at creation time. This is the value you enter in the **OAuth client secret value** field. Microsoft connections track the last processed date rather than UIDs. Each processing cycle queries Microsoft Graph for messages received after the last processed timestamp. The **Start from** date in the [ticketing configuration](/guide/concepts/ticketing#ticketing-configuration) sets the initial boundary — Ekso only processes emails received on or after this date, so historical emails in the mailbox are not accidentally converted into tickets. IMAP and Microsoft 365 use different tracking mechanisms. IMAP resumes by message UID (precise per-message tracking). Microsoft 365 resumes by received date (time-window based). Both approaches prevent duplicate processing, but the Microsoft approach means the **Start from** date on the [ticketing configuration](/guide/concepts/ticketing#ticketing-configuration) is especially important for controlling what gets processed on first activation. Use Microsoft 365 if your organization is already on Microsoft 365. The OAuth approach avoids storing email passwords and supports modern authentication policies like conditional access. ## Credentials All mailbox credentials are encrypted at rest before storage. ## Testing connections Use the **connection test** to verify that Ekso can connect to the mail server with the provided credentials. Test before setting up [ticketing](/guide/concepts/ticketing) to catch configuration issues early. For end-to-end ticketing pipeline testing, see [testing](/guide/concepts/ticketing#testing) in the ticketing guide. # Notifications Source: https://ekso.dev/guide/concepts/notifications Configure scheduled notifications that alert user groups, email addresses, Slack, Teams, or HTTP endpoints when items match your conditions. ## Overview Notifications send scheduled alerts when items match conditions you define. Unlike [webhooks](/guide/concepts/webhooks) (which fire immediately on events), notifications run on a recurring schedule, evaluate a set of filter conditions, and deliver matching items to your chosen recipients. You manage notifications under **Settings > Notifications**. ## How notifications work Each notification follows a three-step cycle: 1. **Evaluate** — on the configured interval, Ekso evaluates the notification's filter conditions against items in the selected containers 2. **Match** — items that satisfy the conditions are collected for delivery 3. **Deliver** — matching items are sent to all configured recipients This cycle repeats on the interval you set, continuously monitoring for items that match your criteria. ## Notification settings | Property | Description | | ----------- | ---------------------------------------------------------------------------------------------- | | Name | Display name for the notification | | Description | What the notification monitors and why | | Active | Whether the notification is currently running | | Container | Which containers to evaluate (select one or more) | | Interval | How often to check for matches (default: every 3 hours) | | Limiter | How many times the same item triggers this notification — `0` for unlimited, `1` for once only | The default interval is **3 hours** (180 minutes). Set a shorter interval for time-sensitive alerts like SLA breaches, or a longer interval for daily digest-style notifications. ### Frequency limiter The limiter controls repeat delivery for the same item: | Value | Behavior | | ----- | ------------------------------------------------------------------------------------- | | 0 | No limit — the item is included every time the notification runs and it still matches | | 1 | Send once — after the item is delivered for this notification, it is not sent again | Use a limiter of `1` for one-time alerts (e.g., "notify when an item is overdue"). Use `0` for recurring digests where you want to see all currently matching items each time. ## Conditions Conditions define which items trigger the notification. They use the same AND/OR group model as [rules](/guide/concepts/rules#conditions): * **Within a group** — conditions are joined with AND (all must match) * **Between groups** — groups are joined with OR (any group can match) Each condition specifies a field, operator, and value. For example: * `Status Equal Blocked` AND `Priority Equal High` — notify when high-priority items are blocked * `DueDate LessThan TODAY` — notify when items are past their due date ## Recipients Notifications support four recipient types. You can combine multiple types on a single notification. ### User groups Select one or more user groups. Every member of the group receives the notification. Use the **Everyone** group to notify all users in the tenant. ### Email addresses Add specific email addresses for recipients who may not have Ekso accounts — for example, external stakeholders or distribution lists. ### Slack and Teams Connect to Slack or Microsoft Teams by specifying a pre-configured connection ID. Notifications are delivered as messages to the connected channel. Configure your Slack or Teams integration first, then reference the connection in your notification recipient configuration. ### HTTP endpoints Send notification payloads to an HTTP endpoint for custom integrations. This is useful for routing notifications to external systems, dashboards, or automation workflows. ## Use cases | Scenario | Configuration | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | SLA breach alert | Condition: `ClockStatus Equal Red`. Interval: 30 minutes. Limiter: 1. Recipients: Support team group. | | Daily overdue digest | Condition: `DueDate LessThan TODAY` AND `Status Not Equal Closed`. Interval: 24 hours. Limiter: 0. Recipients: Project managers group. | | Blocked item escalation | Condition: `Status Equal Blocked`. Interval: 1 hour. Limiter: 1. Recipients: Team leads group + Slack channel. | | Customer ticket monitor | Condition: `Crm Equal Example Inc` AND `Priority Equal High`. Interval: 1 hour. Limiter: 0. Recipients: account manager email. | # Overview Source: https://ekso.dev/guide/concepts/overview Learn how Ekso's core concepts fit together — containers, items, fields, boards, cycles, time tracking, and more. ## Overview Ekso is built around a connected set of concepts that work together: containers organize work, items track it, fields capture data, boards plan delivery, cycles time-box execution, and time tracking measures effort. This page is the map — read it to understand how the pieces fit, then follow the links to learn about each concept in detail. ## Containers and items [Containers](/guide/concepts/containers) are the top-level grouping for work — a project, product, service line, or team workspace. Each container runs one or more processes, defines its own areas, saved filters, and eight-level role-based access control. Containers also control field-level permissions per process, determining who can see, set, and change each field. [Items](/guide/concepts/items) are the core unit of work. Every item belongs to exactly one container and follows a process that defines its fields, features, and workflow. Items support annotations (text, embeds, code snippets), file attachments, cross-reference links, dependency trees, and time entries. A visibility field on both items and individual annotations controls who can see them. ## Process, fields, constraints, and rules [Processes](/guide/concepts/processes) define item types — each process controls which fields appear, which features are enabled, and how items move through workflow status transitions. Every tenant starts with five default processes, and you can create your own. [Fields](/guide/concepts/fields) are the data attributes on items — 38 built-in system fields plus 9 custom field types you can create. Processes control which fields appear on each item type. [Constraints](/guide/concepts/constraints) enforce data integrity by controlling which field values are allowed based on conditions. They ensure your team can only enter valid data combinations. [Rules](/guide/concepts/rules) automate data manipulation when items are created, updated, or when time-based conditions are met. Rules execute asynchronously through a queued engine, and can bypass constraints for automation scenarios. ## Boards and cycles [Boards](/guide/concepts/boards) are planning surfaces that group work for delivery. Each board has a budget with a full audit trail, role-based access control (View, Manage, Plan), an approved resource pool, and an approved container list. Boards are where you plan and allocate resources for sprints, releases, or project phases. [Cycles](/guide/concepts/cycles) are time-boxed iterations within boards. Items are planned into cycles as cards, assigned to resources or job roles, and tracked through four status zones (In Progress, Open, Closed, Blocked). Board managers approve or reject time entries by zone. Closing a cycle finalizes all time data for billing. ## Time tracking and clocks [Time tracking](/guide/concepts/time-tracking) records hours worked on items, split between billable and non-billable categories. Time entries are approved when cycles close, feeding into profitability and utilization reports. [Clocks](/guide/concepts/clock) enforce SLA targets by tracking elapsed time on items. They respect your working calendar and color-code items as green (on time), amber (warning), or red (breach). ## Finance [Finance](/guide/concepts/finance) connects work to money. Job roles define cost and charge rates, SKUs categorize billable work, cost centers group expenses, CRM records link items to customers, and budget reason codes create an audit trail for board budget changes. ## Insights [Insights](/guide/concepts/insights) is the reporting module. It provides profitability analysis grouped by seven dimensions (job role, container, board, cycle, SKU, cost center, or item), per-user time summaries with drilldowns, item-level financial data, and risk reports that flag items where logged time exceeds estimates. ## Mailbox and ticketing [Mailbox connections](/guide/concepts/mailbox) let you connect IMAP or Microsoft 365 mailboxes to Ekso. [Ticketing](/guide/concepts/ticketing) turns inbound emails into items, routes them to the right container and process, and lets agents reply directly from within Ekso. Block lists and domain filters keep unwanted messages out. ## Docs [Docs](/guide/concepts/docs) is Ekso's built-in knowledge base. Organize documents into folders by department or topic, control visibility with user group restrictions, and optionally feed document content to the AI assistant for context-aware answers. Documents can be linked to containers, boards, or items to connect knowledge to the work it relates to. ## Authentication [Authentication](/guide/concepts/authentication) controls how users sign in to Ekso. Forms-based login supports password policies, complexity requirements, and two-factor authentication via email. Microsoft Entra ID enables single sign-on for organizations on Microsoft 365. ## AI [AI](/guide/concepts/ai) adds a built-in assistant and semantic search to Ekso. Ask questions about your work, search by meaning rather than keywords, and use context documents to give the AI access to your organization's knowledge. Ekso provides a default AI provider — you can optionally configure your own OpenAI or Azure OpenAI API key. ## Access control [Access control](/guide/concepts/access-control) manages permissions across your tenant. Application-level access control areas determine who can access boards, insights, finance, docs, timesheets, and containers. Roles are assigned to user groups, and permissions are additive across groups. ## Webhooks [Webhooks](/guide/concepts/webhooks) push real-time event notifications to external systems. When items, users, containers, or boards change, Ekso sends an HTTP POST to the URL you configure — enabling integrations with Slack, Teams, CI/CD pipelines, and audit logging. ## Notifications [Notifications](/guide/concepts/notifications) send scheduled alerts when items match conditions you define. Unlike webhooks (which fire immediately), notifications run on a recurring schedule, evaluate filter conditions, and deliver matching items to user groups, email addresses, Slack, Teams, or HTTP endpoints. ## Workspace [Workspace](/guide/concepts/workspace) settings control your tenant's identity, communication defaults, and AI integration. Configure your organization's profile, welcome message, default AI provider, and email filtering defaults. ## Explore further Settings, areas, processes, saved filters, and access control. Fields, lifecycle, annotations, links, dependencies, and planning. System fields, custom types, and process configuration. Item types with fields, features, workflow, and permissions. Data integrity rules with include and exclude logic. Automated data manipulation and event triggers. Budgets, access control, resources, and containers. Status zones, time approval, and cycle closing. Billable and non-billable time, approval, and reporting. SLA enforcement with working calendars. Job roles, cost centers, SKUs, and CRM. Profitability, time, user, and item reports. IMAP and Microsoft 365 mailbox connections. Email-to-ticket conversion, replies, and spam blocking. Knowledge base with folders, visibility, and AI context. Login methods, password policies, and SSO. AI assistant, semantic search, and provider config. Application-level permissions, roles, and user groups. Real-time event notifications to external systems. Scheduled alerts based on item conditions. Tenant profile, AI provider, and email defaults. # Process Source: https://ekso.dev/guide/concepts/processes Define item types with configurable fields, features, workflow transitions, and field-level permissions. ## Overview A process defines an item type — its fields, features, workflow, and permissions. When you create an [item](/guide/concepts/items), you choose which process it follows. That choice determines which [fields](/guide/concepts/fields) appear, which capabilities are available, and how the item moves through status transitions. You manage processes under **Settings > Process**. Creating, editing, and deleting processes requires the **Super Admin** role. All other users can view process configurations but cannot change them. Each process has five configuration tabs: | Tab | Purpose | | -------- | ----------------------------------------------------------------------------------------- | | Field | Choose which fields appear, set display order, and mark fields as required | | Feature | Toggle capabilities like annotations, attachments, dependencies, links, and time tracking | | Workflow | Define status transitions and control which user groups can make each move | | Clone | Copy a process as a starting point for a new one | | Detail | Configure the process name, description, and icon | Every item follows exactly one process. A single [container](/guide/concepts/containers) can run multiple processes simultaneously — for example, tracking defects, change requests, and tasks each with their own fields, features, and workflow rules. ## Default processes Every new tenant comes with five default processes: | Process | Description | | -------------- | ------------------------------------ | | Defect | Defect triage and management | | New Feature | Introducing additional functionality | | Enhancement | Improving existing functionality | | Change Request | Change request management | | Task | General task management | All five share the same default workflow and field set. They are fully editable — rename them, adjust their fields, change their workflows, delete the ones you do not need, or create entirely new processes using the **+** button. ## Fields The **Field** tab controls which [fields](/guide/concepts/fields) appear on items of this type, their display order, and whether they are required. * **Add or remove fields** — select from system fields and custom fields using the dropdown, then click **Add** * **Set required** — toggle the **Required** switch to make a field mandatory when creating or editing an item * **Reorder** — use the arrow buttons to control the order fields appear on-screen * **Always present** — some fields are always included and cannot be removed: CreateDate, ChangeDate, CreateBy, ChangeBy, RecordSource, EmailGuid, Name, Description, RestrictedTo, OwnedBy, and Status. When you create a new process, these eleven fields are added automatically. Every process must have at least **11 fields**. You cannot save a process with fewer than the mandatory set. The same field can behave differently across processes — required in one, optional in another, or absent entirely. For example, you might require Severity on the Defect process but leave it optional on Task. The Required flag applies when creating and editing items — it does not affect the view screen. The field display order you set here applies uniformly across all three screens (add, update, and view). If you remove a field from a process, that change automatically propagates to every container that uses the process. See [Fields](/guide/concepts/fields) for details on system fields, custom field types, and how field values are used across the application. ## Features The **Feature** tab toggles which capabilities are enabled for the process. Each feature can be independently turned on or off: | Feature | What it enables | | ---------- | --------------------------------------------------------------- | | Annotation | Add annotations to items (text, embeds, code snippets) | | Attachment | Upload file attachments to items | | Dependency | Create parent-child dependency trees between items | | Link | Create cross-reference links between items (duplicate, related) | | Time | Log [time entries](/guide/concepts/time-tracking) against items | When you create a new process, all five features are enabled by default. Disable features you do not need for a process. For example, a simple task process may not need dependencies or time tracking, keeping the interface clean for users. ## Workflow The **Workflow** tab defines status transitions — which status an item can move to from its current status, and who is allowed to make that transition. Each transition rule specifies: | Column | Description | | ------ | ----------------------------------------------------- | | Status | The current status of the item | | Next | The status the item can transition to | | Groups | Which user groups are allowed to make this transition | Ekso recognizes four status types that categorize where an item is in its lifecycle: | Status type | Meaning | | ----------- | ----------------------------------------------------------- | | Open | Item has been created and is ready for triage or assignment | | Working | Item is actively being worked on | | Closed | Item has been resolved or completed | | Blocked | Item cannot progress and is waiting on something | When you create a new process, Ekso generates a default set of transitions: Open → Working, Working → Open, Working → Closed, and Closed → Working — all available to Everyone. For example, after customization, a Defect process might define these transitions: * Open → Assigned (Everyone) * Assigned → Open (Everyone) * Assigned → Closed (Everyone) * Closed → Assigned (Everyone) Every process must have at least **3 workflow transitions**. Add new transitions using the **New Transition** row at the bottom of the list. Each dropdown lets you pick from the available statuses and user groups. User groups control who can make each workflow transition. This means you can restrict certain status changes to specific teams — for example, only QA can move items to "Verified", or only managers can close items. ## Field-level access control Each process in a container has a field-level permission matrix that controls which user groups can interact with each field. The matrix has three columns: | Column | When it applies | | ------ | ------------------------------------------------------------ | | See | When viewing an item — controls whether the field is visible | | Set | When creating an item — controls who can provide a value | | Change | When updating an item — controls who can modify the value | For each field in the process, you assign a user group to each column. This lets you show a field to everyone but only allow managers to change it, or hide sensitive fields from certain groups entirely. Field-level access is configured per process, per container. Navigate to the container's **Process** tab and click the **Access** button next to the process you want to configure. By default, all fields are set to **Everyone** for all three columns. Field-level permissions work alongside [container-level access control](/guide/concepts/containers#access-control). A user needs both container-level access and process-level field access to interact with an item. See [Access control](/guide/concepts/access-control) for the full permission model. ## Clone and detail * **Clone** creates a copy of the process as a starting point for a new one. This is the fastest way to create a new process that shares most of its configuration with an existing one. * **Detail** configures the process name, description, and icon displayed in process lists and item headers. ## Content templates Processes support content templates that pre-fill descriptions or checklists when creating new items. Templates are scoped to a specific container and process combination — so you can have different default content for Defect items in your "Support" container versus your "Engineering" container. ## Process and containers Processes are global templates defined under **Settings > Process**. Containers enable or disable individual processes via their **Process** tab. This means: * A process is configured once and can be used across multiple containers * Each container chooses which processes are available for creating items * Field-level access control is configured per container, allowing different permission rules for the same process in different containers Changes to a process cascade to all containers that use it. If you remove a field from a process, it is removed from every container's configuration for that process. If you delete a process entirely, it is removed from all containers. Deleting a process affects every container that uses it. Check the process stats on the **Settings > Process** page to see how many items currently follow a process before removing it. See [Containers](/guide/concepts/containers) for details on container settings, areas, labels, saved filters, and role-based access control. # Rules Source: https://ekso.dev/guide/concepts/rules Automate workflows with event and timer triggers that update fields, send notifications, and enforce business logic. ## Overview Rules automate data manipulation on items. When an item is created, updated, deleted, or when a timer interval elapses, rules evaluate conditions and set field values automatically. They are the automation engine behind Ekso's workflow capabilities — used to send notifications, update fields, create items, and drive integrations. Rules do not prevent actions — they react to them. If you need to block an invalid action, use a [constraint](/guide/concepts/constraints) instead. You manage rules under **Settings > Rule**. Each rule has three configuration tabs: **Conditions**, **Actions**, and **Detail**. ## Creating a rule The **Detail** tab configures the rule's identity and trigger. | Property | Description | | ----------- | ------------------------------------------------------------------------------------------------ | | Name | The rule name. Rules execute in alphabetical order, so use numeric prefixes to control sequence. | | Description | Rich text description of what the rule does | | Owner | Which part of the system owns the rule (e.g., System) | | Container | Which containers the rule applies to (select **All** for tenant-wide) | | Trigger | How the rule fires — **Event** or **Timer** | | Event | When trigger is Event: which event fires the rule | | Timer | When trigger is Timer: interval in minutes between evaluations | | Deleted | Toggle to remove the rule from the system | Name your rules with numeric prefixes to control execution order. For example, `01-set-priority`, `02-assign-area`, `03-notify`. This makes the sequence explicit and easy to reorder. ## Conditions The **Conditions** tab defines when the rule fires. Each condition is a field–operator–value check (for example, `Crm Equal Fenrise Inc` or `Severity Equal Minor`). Conditions are organized into groups: * **Within a group** — conditions are combined with AND (all must match) * **Between groups** — groups are combined with OR (any group can match) This gives you flexible matching. For example: "fire this rule if the CRM is Fenrise Inc **OR** the Severity is Minor." Text fields support date tokens and arithmetic comparisons in their values (see [Tokens](#tokens) below). ## Actions The **Actions** tab defines what happens when the rule fires. Ekso supports two action types, and a rule can mix both. ### Field value actions Set a field on the triggering item to a new value. A rule can have multiple field value actions — for example, set Priority to `High` **and** set Approved to `true` in a single rule. Field value actions can update any field on the item, including fields that would normally be restricted by [constraints](/guide/concepts/constraints). This is by design — rules act as a data manipulation escape hatch for automation. ### Endpoint actions POST the rule-fire envelope to an external URL. Use endpoint actions to trigger Slack alerts, Linear ticket creation, PagerDuty incidents, or any webhook-style integration. Endpoint actions are **fire-and-forget** — Ekso does not inspect the response status. Private, loopback, and cloud metadata URLs are blocked to prevent server-side request forgery. See [Rule endpoint integrations](/guide/integrations/rule-endpoints) for the envelope shape, URL restrictions, and receiver examples. ## Trigger types ### Event triggers Event rules fire when specific item lifecycle events occur. | Event | When it fires | | --------------- | ------------------------------------------------------------------------------------------------------- | | On Item Add | During item creation, before the item is saved. Allows data manipulation before the user sees the item. | | On Item Added | After an item has been created and saved. | | On Item Updated | After an item has been modified. | | On Item Deleted | After an item has been deleted. | **On Item Add** vs **On Item Added**: "Add" fires *before* save and can manipulate the item before the user sees it (setting defaults, auto-assigning fields). "Added" fires *after* save, when the item already exists in the system. ### Timer triggers Timer rules fire on a recurring interval rather than in response to a user action. You specify the interval in minutes (for example, every 10 minutes). Each time the timer fires, the rule evaluates its conditions against all matching items and applies actions to those that match. Use timer rules for deadline-based automation — for example, checking whether a due date has passed and updating the status to `Overdue`, or sending reminder notifications. ## Tokens Rules support tokens that resolve to dynamic values at execution time. Use tokens in condition values and action values to set dates relative to the current moment. | Token | Resolves to | | ------------- | -------------------------------- | | `TODAY` | Current date | | `WEEK_START` | Start of the current week | | `WEEK_END` | End of the current week | | `MONTH_START` | First day of the current month | | `MONTH_END` | Last day of the current month | | `+n` | n days after the reference date | | `-n` | n days before the reference date | Combine tokens with offsets for relative dates. For example, `TODAY+7` means seven days from today. `MONTH_END-1` means the day before the end of the current month. ## Queued execution The rule engine processes rules through a queue. Rules do not execute inline during the user's action — they are queued and processed asynchronously. Because rules execute asynchronously, changes may not appear instantly after item creation or update. The queue processes rules in order, and the effects will be applied shortly after the triggering action. ## Rules and constraints Rules bypass [constraints](/guide/concepts/constraints). While constraints prevent users from setting invalid field values, rules can set any value regardless of constraint restrictions. This separation is intentional: * **Constraints** protect against user error and enforce data quality for manual actions * **Rules** handle automated workflows where strict constraint enforcement would be counterproductive For example, a rule might set a status that is normally blocked by a constraint, because the automated workflow requires it. The constraint still protects against users making the same change manually. # Ticketing Source: https://ekso.dev/guide/concepts/ticketing Turn inbound emails into tickets, reply from within Ekso, manage conversations, and block spam. ## Overview Ekso's ticketing system turns inbound emails into work [items](/guide/concepts/items) and lets agents reply directly from within the application. When a customer sends an email, Ekso creates a ticket in the right [container](/guide/concepts/containers) with the right [process](/guide/concepts/processes). When an agent responds, the reply goes back to the customer by email. If the customer replies again, Ekso threads the response back to the same ticket — maintaining a complete conversation history. This is Ekso's built-in help desk capability: two-way email ticketing with threading, acknowledgement emails, reply templates, and domain-based filtering. Before configuring ticketing, you need at least one [mailbox connection](/guide/concepts/mailbox). You manage ticketing configurations under **Settings > Ticketing**. ## Ticketing configuration A ticketing configuration maps a mailbox to a destination container and process. This is what turns raw emails into structured tickets. You can create multiple ticketing configurations — for example, one that routes support emails to a "Support" container and another that routes sales enquiries to a "Sales" container. | Property | Description | | --------------------- | --------------------------------------------------------------------------- | | Name | Display name for this ticketing configuration | | Mailbox | Which mailbox connection to use | | Container | Destination container for inbound tickets | | Process | The process type for inbound tickets | | Domain filter | Email domains to allow or block (see [Domain filtering](#domain-filtering)) | | Acknowledgement email | Subject and body template for new ticket confirmations | | Reply email | Subject and body template for threaded replies | | Start from | Earliest date to process tickets from | ### Domain filtering Domain filtering controls which email senders can create tickets. Two modes are available — they are mutually exclusive: * **Only these email domains are allowed** — whitelist mode: only emails from the listed domains create tickets. All others are skipped. * **These email domains are disallowed, all others are allowed** — blacklist mode: emails from the listed domains are blocked. All other domains are accepted. If no domains are listed, all senders are accepted. Domain filtering on the ticketing configuration is separate from the global block lists in [general mail settings](#block-lists). Global block lists are evaluated first, then the per-ticketing domain filter. ### Email templates Each ticketing configuration has two email templates that control what the sender receives: **Acknowledgement email** — sent when a new ticket is created from an inbound email. This confirms receipt to the sender. **Reply email** — sent when an agent adds a response to an existing ticket. This delivers the agent's reply back to the original sender. Both templates have a subject line and a rich text body. Templates use tags that are replaced with real values at send time: | Tag | Required in | Resolves to | | ----------- | ------------------- | ------------------------------- | | `#code#` | Subject (mandatory) | The item key (e.g., `GEM-123`) | | `#name#` | Subject (optional) | The item name | | `#content#` | Body (mandatory) | The annotation or reply content | The `#code#` tag in the subject line is critical for threading. When Ekso sends an acknowledgement or reply, the item key (like `[GEM-123]`) is embedded in the subject. When the customer replies, their email client preserves that subject line. Ekso then extracts the key from the inbound reply and matches it to the original item. Without the `#code#` tag, this threading loop breaks and every reply creates a new ticket instead of continuing the conversation. ### Testing After configuring a ticketing pipeline, use the **ticketing test** to run a full end-to-end processing cycle. This converts any available emails in the connected mailbox into tickets, verifying the complete pipeline — domain filtering, thread detection, item creation, and email tagging. Test before activating a ticketing configuration to catch configuration issues early. ## How ticketing works The ticketing engine processes emails every 5 minutes. Here is the complete flow: Ekso checks the configured inbox folder for new messages. For IMAP, it queries for unseen messages by UID. For Microsoft 365, it queries by received date using Microsoft Graph. Each email is checked against the global block lists (domain and subject) and the ticketing domain filter. Emails that fail are tagged as **EksoSkipped** and ignored. Ekso scans the email subject and body for an item key pattern — a container code followed by a sequence number in square brackets, like `[GEM-123]`. The pattern matches 2–5 letter codes followed by a hyphen and number. If found and the item exists, this email is a reply to an existing ticket. * **New email** (no item key found): A new item is created in the target container and process. The email subject becomes the item name, the cleaned body becomes the description, and attachments are preserved. An **acknowledgement email** is sent to the sender, with the new item key in the subject. * **Reply to existing ticket** (item key found): The email content is added as an annotation (comment) on the existing item. Attachments are added to the annotation. A **reply confirmation email** is sent to the sender. The processed email is tagged as **EksoProcessed** so it is not processed again. The ticketing configuration's tracking position is updated (UID for IMAP, timestamp for Microsoft). ### Reply threading The threading cycle works as follows: 1. Customer sends an email → Ekso creates a ticket (e.g., `GEM-123`) → acknowledgement email goes out with `[GEM-123]` in the subject 2. Agent responds from within Ekso → reply email goes out to the customer with `[GEM-123]` in the subject 3. Customer replies → their email client preserves the `[GEM-123]` subject → Ekso extracts the key and adds the reply as an annotation on item `GEM-123` 4. The cycle repeats — every reply from either side threads to the same item This maintains a complete conversation history on a single item, visible to all agents working the ticket. ### Email content cleaning Emails often contain HTML formatting, tracking pixels, scripts, and quoted reply chains. Ekso sanitizes all inbound email content before storing it as an item description or annotation: * **Script and style removal** — all `