Are there specifications for the 4 endpoints in the Forge Assets Import module?

Also @ibuchanan , is there anywhere that fully describes the contract of a custom import app? There are 4 endpoints

    - key: onDeleteImport
      handler: index.onDeleteImport
    - key: startImport
      handler: index.startImport
    - key: stopImport
      handler: index.stopImport
    - key: importStatus
      handler: index.importStatus

But the extension points documentation does not explain the purpose of each, nor the arguments/return types and expected/supported exceptions. From your example it appears that the only valid return type is the success object:

    return {
      result: "start import",
    };

and all fail paths should just throw new Error("some message"). This pattern seems the same for all four extension points. And as far as i can tell, the purpose of each is:

  • onDeleteImport: the import source itself is deleted, perhaps you have some persistent objects in kv store or something to clean up and this is when you would do that
  • startImport: the user clicks the Import data button and you should start using the Assets importsource api’s to create a new execution then submit data, possibly update status, and eventually submit the last chunk of data with attribute "completed": true notifying the assets feature that data upload is complete and it can start the real work of import. because of timeouts, this is typically funneled off to one or more queues (Forge Consumer for background processing. there is typically some other secondary queue for watching progress of the import? but its unclear if anything additional can/should happen… its also unclear if people can/should have bolt on post processing (like i still cannot figure out how to set attributes that are references to other objects like users, so maybe i need a post-processor for this)
  • stopImport: after import has been started, the user clicks the Cancel import button and this should delete the current execution of the importsource.
  • importStatus: reports the current status of the importsource itself, NOT a currently running execution of the import. this seems to have a contract that requires some sort of success to allow/disallow the Import data button, but its unclear if what that is.

On top of this, i am trying to figure out if/how i can push data into this because in my scenario it is unlikely that the import process will be able to reach out to the corporate network to pull the data i need to add to assets, so i need some way to push. I am considering (mis)using webhooks to do that, but that seems wrong because webhooks dont have authn/authz…

I know this is a lot of questions, but hopefully you can help sort this out (and maybe update the documentation for future users like me).

The jiraServiceManagement:assetsImportType module does not support “push” strategy in the way you describe.

I think you have the gist of the 4 endpoints. Also, I have some draft docs that I could publish here for details about the 4 endpoints. But do you still need that if you can’t push?

Forge Remote is probably the closest auth model that fits. Unfortunately, it still requires endpoints that Atlassian can reach.

If you do reach for webhooks, you can “roll your own” auth. Here’s an example (contrasted with the similar api-route module): GitHub - ibuchanan/explore-forge-jira-custom-api: A Forge app that exposes a clean REST API for creating Jira issues without exposing Jira's internal field model to callers. · GitHub

@ibuchanan

I think you have the gist of the 4 endpoints. Also, I have some draft docs that I could publish here for details about the 4 endpoints. But do you still need that if you can’t push?

Yes, it would be nice to see your draft documentation. Like i said, i am considering (mis)using the webtriggers to push code (with your suggested authn/authz approach) to preload the data into kv store values then use that as the source for this import process. I cant really think of another process by which a could synchronize a set of assets on a schedule (probably 2 times a day). On top of that i need to associate the assets with atlassian user, which seems to be done by an attribute, but since built in import doesn’t seem to do that i am thinking i would have my controller queue watch for the completion of the importsource execution then just start a new loop of processes that goes over every asset not already associated and searches for user by email to update the attribute one by one.

I imagine there may be easier ways to accomplish this but the documentation seems to still be a work in progress and everything not explicitly in the documentation is fairly opaque so its hard to know if i am missing something.

Forge module: jiraServiceManagement:assetsImportType

The jiraServiceManagement:assetsImportType module displays a modal that allows users to configure their Forge-based imports with information such as login details or configuration information for their app.

The modal appears when a user selects a schema within Assets, then selects Schema configuration, then selects Import, then selects their import type, then selects Configure App in the dropdown.

The module also contains functions for optional use - onDeleteImport, startImport, stopImport, importStatus.

The content of the module is rendered below the text Configure {Import Structure Name} and above the Save Configuration and Cancel buttons.

Properties

Property Type Required Description
key

string

Yes

A key for the module, which other modules can refer to. Must be unique within the manifest.

Regex: ^[a-zA-Z0-9_-]+$

description

A description of the Assets Import Type that displays under it.

title Yes

The name of an Assets Import Type, which is displayed on each of the import structure cards. They live on the Imports tab of each Object Schema in Assets.

icon string Yes
onDeleteImport { function: string } Contains a function property, which is executed on deletion of an Assets Import Type.

Regex: ^[a-zA-Z0-9_-]+$

startImport { function: string } Yes Contains a function property, which is executed when an import of this Assets Import Type is started.

Regex: ^[a-zA-Z0-9_-]+$

stopImport { function: string } Yes Contains a function property, which is executed when an import of this Assets Import Type is cancelled.

Regex: ^[a-zA-Z0-9_-]+$

importStatus { function: string } Yes Contains a function property, which is executed when Imports UI is loaded to display the status of the import.

There are two status enums that can be returned currently,{ status: "NOT_CONFIGURED" } or { status: "READY" }

Regex: ^[a-zA-Z0-9_-]+$

Extension context

UI Kit and Custom UI

Use the useProductContext hook to access the extension context in UI Kit or getContext bridge method in Custom UI.

Property Type Description
type string The type of the module.
importId string Distinguishes between different import jobs, allowing multiple imports from the same data source type
workspaceId string Identifies which Assets workspace/schema collection contains the target object types
schemaId string Identifies the specific object type where imported data will be stored as objects

Lifecycle extension point functions

interface AssetsImportContext {
	contextToken: string; // Identifies invocation of this Assets import lifecycle extension point, typically not used in code
	importId: string; // Distinguishes between different import jobs, allowing multiple imports from the same data source type
	workspaceId: string; // Identifies which Assets workspace/schema collection contains the target object types
	schemaId: string; // Identifies the specific object type where imported data will be stored as objects
	context: ForgeContext; // Standard Forge context with user/account information
}

interface ForgeContext {
	accountId: string;
	cloudId: string;
	localId: string;
	moduleKey: string;
	extension: {
		importId: string;
		workspaceId: string;
		schemaId: string;
		type: string;
		// Note: executionId is NOT provided here - apps must create it via the Assets API
	};
	userAccess: {
		enabled: boolean;
		hasAccess: boolean;
	};
}

TODO: Specify each of the 4 function hooks below

Import lifecycle extension points

Each lifecycle function receives an AssetsImportContext and executes asynchronously, returning plain JavaScript objects.

Note about executionId: The executionId is not provided in the context for the startImport function. Your app must create a new execution via the Assets API and extract the executionId from the response. For implementation details, see the Assets Import App how-to guide.

Best practices for lifecycle functions

When implementing lifecycle extension points, follow these guidelines:

Error handling:

  • Throw an Error for validation failures or unexpected conditions
  • Error messages will be displayed to users in the Assets UI
  • For importStatus, return { status: "NOT_CONFIGURED" } as a safe default if the API call fails (do not throw errors)

Validation:

  • Always validate required context fields (importId, workspaceId, schemaId)
  • Provide clear error messages that help users understand what’s wrong

API calls:

  • Use api.asApp() for server-to-server calls that don’t require user permissions
  • Use api.asUser() when you need to respect user-level permissions

onDeleteImport

Executed when a user deletes an import configuration in Assets UI.

Function signature:

async function onDeleteImport(context: AssetsImportContext): Promise<ImportResult>

Return value:

{ result: string }

Example:

export async function onDeleteImport(context: AssetsImportContext): Promise<ImportResult> {
  return { result: "on delete import" };
}

startImport

Executed when a user clicks the “Import data” button in Assets UI to begin an import.

Function signature:

async function startImport(context: AssetsImportContext): Promise<ImportResult>

Context fields:

  • importId: The import configuration identifier
  • workspaceId: The target Assets workspace
  • schemaId: The target object schema

Return value:

{ result: string }

Example:

export async function startImport(context: AssetsImportContext): Promise<ImportResult> {
  const { workspaceId, importId } = context;
  
  // Create execution via Assets API
  const endpoint = route`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/executions`;
  const response = await api.asApp().requestJira(endpoint, { method: "POST" });
  const executionData = await response.json();
  
  // Queue background work
  await controllerQueue.push({ body: { importId, workspaceId, executionId } });
  
  return { result: "start import" };
}

stopImport

Executed when a user cancels an active import in Assets UI.

Function signature:

async function stopImport(context: AssetsImportContext): Promise<ImportResult>

Return value:

{ result: string }

Example:

export async function stopImport(context: AssetsImportContext): Promise<ImportResult> {
  return { result: "stop import" };
}

importStatus

Executed when Assets UI loads to display the current status of an import. Used to determine whether the “Import data” button should be enabled.

Function signature:

async function importStatus(context: AssetsImportContext): Promise<ImportStatusResult>

Return value:

{ status: "NOT_CONFIGURED" | "READY" }

Status meanings:

  • NOT_CONFIGURED: Import mapping has not been configured or is incomplete. The “Import data” button will be disabled.
  • READY: Import is configured and ready to run. The “Import data” button will be enabled.

Example:

export async function importStatus(context: AssetsImportContext): Promise<ImportStatusResult> {
  const { importId, workspaceId } = context;
  const response = await api.asApp().requestJira(
    route`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/configstatus`
  );
  const data = await response.json();
  
  return data.status === "MISSING_MAPPING" 
    ? { status: "NOT_CONFIGURED" } 
    : { status: "READY" };
}

Assets Import REST API

The following Assets REST API endpoints are commonly used in conjunction with the import lifecycle extension points.

Report import failure

Endpoint: POST /jsm/assets/workspace/{workspaceId}/v1/importsource/{importId}/executions/{executionId}/history/failed

Purpose: Reports a failure for an import execution to the Assets import history.

Request body:

{
  failureReason: string  // Required, max 1024 characters
}

Requirements:

  • Must be called after an execution has been created (requires valid executionId)
  • The failureReason field is required and cannot exceed 1024 characters
  • Failure will be visible in the Assets import history with the provided reason

Common failure scenarios:

  • Validation errors: Import configuration is invalid or incomplete
  • Third-party API errors: Fetching data from external services failed
  • Authentication failures: Credentials or tokens are expired
  • Data transformation errors: Data cannot be properly transformed
  • Rate limiting: Third-party API rate limits exceeded

Scheduled imports

Create schedule endpoint: POST /jsm/assets/workspace/{workspaceId}/v1/importsource/{importId}/importschedule

Request body:

{
  runFrequency: "ONCE" | "DAILY" | "WEEKLY" | "MONTHLY",
  startTime: string,        // ISO 8601 datetime
  timezone: string,         // IANA timezone identifier
  callbackUrl: string       // Your app's webtrigger URL
}

Response:

{
  id: string,               // importScheduleId for future operations
  // ... other schedule metadata
}

Webtrigger request:

  • Method: POST
  • Body:
    {
      workspaceId: string,
      importsourceId: string
    }
    

Webtrigger handler requirements:

  • Include "scheduled": true in the executions API request body
  • Use api.asUser() for API calls (user-initiated data flows)
  • Return HTTP 200 for success, 500 for errors

Constraints:

  • Schedule requires that the import source is already fully configured with a valid mapping
  • If a scheduled import triggers while a manual import is running, the scheduled import will fail with an error
  • Import execution history shows who triggered the import (manual user vs. scheduled service)
  • If tenant uninstalls the app, Assets backend automatically deletes associated schedules at the next scheduled date