Canonical page: https://tellenze.com/help/apps

Connect other systems with Apps

Authorize member App creation, grant project access, and use the API with clear App and member attribution.

Decide who can create Apps

Admins can create Apps by default. To let other active members create them, open Administration → Settings, enable Allow members to create Apps, and save. Member creation starts disabled. Turning it off stops new member-created Apps; existing Apps keep their access until their owners revoke them. Each App belongs to the member who creates it.

Create and maintain an App

Open Account settings → Apps. Enter a recognizable name, choose its projects and permissions, then create it. Workspace documentation is a separate optional grant for Knowledge outside a project. Copy the token from the confirmation: it appears only once. Store it in the connecting system's secret store.

Edit changes the name, image or grants. Rotate token immediately invalidates the old token and displays its replacement once. Revoke stops further access. Other members cannot manage your credentials. If a create or rotate response is lost, check the list and deliberately rotate; plaintext tokens cannot be recovered.

Apps stay limited by their owner's current membership and project access. Archived projects, revoked Apps and suspended owners are unavailable. The settings page gives the API address for this workspace; credentials from another workspace do not work.

Add an App image

Choose Upload image when creating or editing an App. PNG, JPEG, GIF and WebP images up to 2 MiB and 1024 × 1024 pixels are supported. The form previews your selection. Use Replace image to choose another file or Remove image to return to the default App symbol, then save.

Your image appears beside the App in settings, reporter and author details, document revisions and activity. Existing contributions use the current image when loaded, while their recorded App name and responsible member stay unchanged. Revoking an App preserves its image for historical attribution.

Images are scanned before saving, stored privately and available only to active members of the same workspace. They count toward member and workspace storage and share the attachment upload limits. If scanning or validation fails, the current image is preserved; correct the file or retry when scanning is available. Missing images use the default App symbol.

The dimension limit applies to the actual pixels in the file, not its displayed size or file size. A dimension error shows the detected width and height; export or resize the image to fit within 1024 × 1024. A scanning-unavailable message means the upload service could not check the file, not that the image is infected. Local developers must run ClamAV and configure its connection as described in the README's local file-upload setup.

Choose permissions

Permission Access
projects:read Read granted active projects and their workflow stages.
focus-groups:read Read Focus groups in those projects, when Focus is enabled.
work-items:read Read active work items in granted projects.
work-items:write Create work items and update their supported definition fields.
documents:read Read granted project documentation, plus workspace documentation when enabled.
documents:write Create and update documents in those locations.
signals:write Submit shared workspace reports or append evidence on an eligible paid subscription.
signals:read Discover shared workspace Signals, with project-derived details restricted to App grants and current owner access.

Projects and Focus groups are read-only. Apps cannot manage members, permissions or Required Context policies. Resource responses exclude unrelated linked work, private repository paths and attachments.

Set up the examples

Copy the API address from Account settings → Apps. It ends in /api/v1/apps. Replace the example address and token below, and omit a trailing slash from the address. These commands use a POSIX-style shell, such as Bash or Zsh.

export TELLENZE_APP_API='https://your-workspace.example.com/api/v1/apps'
export TELLENZE_APP_TOKEN='YOUR_APP_TOKEN'

Every request sends Authorization: Bearer YOUR_APP_TOKEN and Accept: application/json. Writes also send Content-Type: application/json. The examples use SUPPORT as a project key: replace it with a project granted to your App. Replace sample record IDs and versions with values returned by your workspace.

Enable the permission for each operation you use. Read and write permissions are separate; for example, the read-then-update work-item example needs both work-items:read and work-items:write. Workspace documents additionally need the Workspace documentation grant. If Required Context applies, follow Supply Required Context below before sending a write.

Read projects and work items

Method Path
GET /projects or /projects/{key-or-id}
GET /focus-groups or /focus-groups/{id}
GET /work-items or /work-items/{identifier-or-id}
GET /documents or /documents/{id}

Responses use a data object or list. Archived records and records outside current grants are excluded. List the projects your App can access, then inspect one project's workflow stages:

curl "$TELLENZE_APP_API/projects" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json'

curl "$TELLENZE_APP_API/projects/SUPPORT" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json'

The project response includes data.workflow_stages. A stage's id can be used as workflow_stage_id when creating work; choose one whose is_completion is false.

List work in one project, then read a single ticket by its identifier:

curl --get "$TELLENZE_APP_API/work-items" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  --data-urlencode 'project_key=SUPPORT' \
  --data-urlencode 'per_page=50'

curl "$TELLENZE_APP_API/work-items/SUPPORT-42" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json'

All lists accept per_page from 1 to 100 (default 25). Work-item, Focus and document lists accept project_key. Pagination uses cursors: follow the complete URL in links.next with the same authorization and Accept headers until it is null. Do not construct page numbers. For example, after replacing the placeholder with a returned next-page URL:

NEXT_PAGE_URL='PASTE_THE_LINKS_NEXT_URL_HERE'
curl "$NEXT_PAGE_URL" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json'

Read Focus groups and documentation

Focus must be enabled in the workspace. With focus-groups:read, list a project's groups, then copy an id from data to read one. The ID below is illustrative:

curl --get "$TELLENZE_APP_API/focus-groups" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  --data-urlencode 'project_key=SUPPORT'

FOCUS_GROUP_ID='01ARZ3NDEKTSV4RRFFQ69G5FAV'
curl "$TELLENZE_APP_API/focus-groups/$FOCUS_GROUP_ID" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json'

With documents:read, list project documentation or workspace Knowledge. The special project_key=workspace filter applies only to documents and requires the Workspace documentation grant. Without a filter, the document list includes all locations allowed by your grants.

curl --get "$TELLENZE_APP_API/documents" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  --data-urlencode 'project_key=SUPPORT'

curl --get "$TELLENZE_APP_API/documents" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  --data-urlencode 'project_key=workspace'

Copy a document's id from the response to read it. Document and Focus URLs use IDs, not titles or slugs.

DOCUMENT_ID='01ARZ3NDEKTSV4RRFFQ69G5FAW'
curl "$TELLENZE_APP_API/documents/$DOCUMENT_ID" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json'

Report a ticket from another system

With work-items:write, the smallest creation payload contains project_key and title. Use a stable Idempotency-Key for each logical write, such as the provider and external ticket ID. It must contain 8–128 letters, numbers, periods, underscores, colons or hyphens.

curl "$TELLENZE_APP_API/work-items" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-ticket-1842' \
  --data '{"project_key":"SUPPORT","title":"Checkout fails on mobile"}'

For a richer report, send Markdown in description and acceptance_criteria. This example is a separate ticket, so it uses a different key. The quoted JSON delimiter keeps the shell from expanding the payload.

curl "$TELLENZE_APP_API/work-items" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-ticket-1843' \
  --data-binary @- <<'JSON'
{
  "project_key": "SUPPORT",
  "title": "Payment confirmation email is missing",
  "description": "## External report\nSource: [Ticket 1843](https://support.example.com/tickets/1843)\n\nPayment succeeds, but the customer receives no confirmation email.",
  "acceptance_criteria": "- [ ] A successful payment sends one confirmation email.\n- [ ] Retrying the payment callback does not send a duplicate.",
  "type": "bug",
  "due_date": "2026-10-15",
  "blocked": true,
  "blocked_reason": "Waiting for the email provider's delivery logs."
}
JSON

Adjust the date and choose a work type enabled for your project. A successful create returns HTTP 201. This response excerpt shows the values to keep for later requests; your identifier and other values will differ:

{
  "data": {
    "identifier": "SUPPORT-42",
    "title": "Payment confirmation email is missing",
    "version": 1,
    "blocked": true
  }
}

Save the external ticket ID's mapping to data.identifier in your integration. The API assigns the reporter and App attribution automatically.

Other optional definition fields are complexity, parent_id, assignee_ids and label_ids. Complexity must use your workspace's estimation scale. Assignees are numeric member IDs with access to the project; labels and parent work items use IDs from the same project. This API does not expose member or label directories, so obtain those IDs from your existing workspace configuration.

Choose a starting stage and Focus

Creation can also include workflow_stage_id from the project response. For Focus assignment, read the Focus group again and send its data.id as focus_group_id and its current data.version as focus_group_version. Replace both illustrative IDs and the version in this payload, then use it as the body of POST /work-items with its own idempotency key:

{
  "project_key": "SUPPORT",
  "title": "Investigate delayed payment callbacks",
  "workflow_stage_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
  "focus_group_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "focus_group_version": 3
}

Choose a non-completion stage and a Planned or Active Focus in the same project. Focus must be enabled. Omit these fields to use the project's default starting stage with no explicit Focus assignment. You may send a supported non-completion status instead of workflow_stage_id, but do not send both.

Update an existing work item

Read GET /work-items/{identifier-or-id} first and use its current data.version. For an item currently at version 1, this request replaces its description and unblocks it:

curl --request PATCH "$TELLENZE_APP_API/work-items/SUPPORT-42" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-ticket-1843-update-1' \
  --data-binary @- <<'JSON'
{
  "version": 1,
  "description": "The provider delivered its logs. Investigation can continue.",
  "blocked": false
}
JSON

HTTP 200 returns the updated resource and its new version. Omitted definition fields stay unchanged; description and acceptance_criteria replace their previous text rather than append. Unblocking also clears the blocked reason. Send due_date: null to clear a due date, or an empty assignee_ids or label_ids array to remove those assignments. Non-empty assignment arrays replace the corresponding selection.

Stage changes, shipping, changing project and Focus reassignment remain in the existing workspace workflow controls; these are not supported by this PATCH endpoint. A stale version returns 409: read the record again, reconcile your intended changes with the current values, and use its new version and a new idempotency key for the revised edit.

Write documentation with clear attribution

With documents:write, create a project document by sending its project key and title. The Markdown body, slug and folder are optional:

curl "$TELLENZE_APP_API/documents" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-runbook-create-1' \
  --data-binary @- <<'JSON'
{
  "project_key": "SUPPORT",
  "title": "Payment incident runbook",
  "slug": "payment-incident-runbook",
  "body": "# Payment incidents\n\n1. Find the external ticket.\n2. Check the provider's delivery logs.\n3. Record the outcome on the work item."
}
JSON

HTTP 201 returns the document under data; save its id and version. A slug uses lowercase letters, numbers and single separating hyphens, and must be unique in that project or workspace location. Omitting it derives a slug from the title. A supplied folder_id must belong to the same location; omitting it creates the document at the root.

To create workspace Knowledge, use project_key: "workspace" and enable the App's Workspace documentation grant:

curl "$TELLENZE_APP_API/documents" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: workspace-support-handoff-1' \
  --data-binary @- <<'JSON'
{
  "project_key": "workspace",
  "title": "Support handoff checklist",
  "body": "# Handoff\n\n- Link the external ticket.\n- Record the affected project.\n- Note the next action."
}
JSON

Omitting project_key also targets workspace Knowledge, so always include a project key when you intend to create project documentation.

Work-item reporter details show the App and responsible member. Documents show them in Created by, Last updated by and revision history. App names are captured when each change happens; renaming or revoking an App does not rewrite attribution. Clients cannot choose another reporter, author or origin.

Update a document

Read the document first using the earlier GET example. Set DOCUMENT_ID to its actual data.id, then replace the version below with its current data.version:

curl --request PATCH "$TELLENZE_APP_API/documents/$DOCUMENT_ID" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-runbook-update-1' \
  --data-binary @- <<'JSON'
{
  "version": 1,
  "title": "Payment incident runbook — revised",
  "body": "# Payment incidents\n\n1. Find the external ticket.\n2. Check the provider's delivery logs.\n3. Confirm the customer received the email.\n4. Record the outcome."
}
JSON

HTTP 200 returns the new version. Each successful create or update creates an immutable revision. A supplied body replaces the entire Markdown body; omitted fields remain unchanged. You can change title, body and folder_id. Use folder_id: null to move a document to its location's root. Project scope and slug cannot be changed through this endpoint.

Retry a write safely

Keep each write's JSON body and idempotency key until you know its outcome. If the connection drops, send the same method, URL, key and JSON body again. For example, retrying the minimal ticket creation above with response headers visible:

curl --include "$TELLENZE_APP_API/work-items" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-ticket-1842' \
  --data '{"project_key":"SUPPORT","title":"Checkout fails on mobile"}'

If the original write succeeded, Idempotency-Replayed: true identifies the replay. It returns the existing resource without duplicating a work item, document or revision. Retries recheck current grants and return current resource values, so the returned version may be newer than the original response. They never expose a cached private body.

Reusing a key for a different method, path or JSON body returns 409. Give each new logical write its own key, including updates to an existing external ticket. Do not change a key or refresh the body merely because a response was lost. A confirmed validation or version error should be corrected before sending a revised request with a new key.

Supply Required Context

Where a policy applies, send POST /required-context with exactly one target:

Write you are preparing Context request body
Create a work item in SUPPORT {"project_key":"SUPPORT"}
Update SUPPORT-42 {"task":"SUPPORT-42"}
Create or update a SUPPORT document {"project_key":"SUPPORT"}
Create or update workspace Knowledge {"workspace":true}

The context endpoint requires the applicable write permission but does not require an idempotency key. This example fetches guidance before creating a SUPPORT ticket. The following walkthrough uses jq to read JSON and safely construct a payload; you can perform the same steps with your integration's JSON library.

curl --fail --silent --show-error "$TELLENZE_APP_API/required-context" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data '{"project_key":"SUPPORT"}' \
  --output required-context.json &&
  jq -r '.data.revisions[] | "# " + .title + "\n\n" + (.body // "")' required-context.json

Only continue if the request succeeds and you have read every returned revision. Then include the returned receipt in the write. This produces ticket-with-context.json; keep that file unchanged for any retry with the same key:

jq '{
  project_key: "SUPPORT",
  title: "Investigate a delayed payment callback",
  description: "Reported by the support integration after reading Required Context.",
  required_context_receipt: .data.receipt
}' required-context.json > ticket-with-context.json

curl "$TELLENZE_APP_API/work-items" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-ticket-1844' \
  --data-binary @ticket-with-context.json

For an update, fetch context for the target in the table and add required_context_receipt alongside the current resource version and changed fields. A project receipt cannot be reused for a task-targeted update.

The receipt acknowledges delivery, not comprehension. Receipts expire after 24 hours and bind the workspace, App, owner, App settings version, target and exact policy context. Changed policies or grants require new reading. Missing or inaccessible guidance blocks the write. The App needs document-read permission and applicable project/workspace grants for the guidance. If no policy applies, data.receipt is null and data.revisions is empty; no reading is required and the null receipt can be sent or omitted.

Handle errors

Status Next step
401 Check the token, workspace address, revocation and active owner.
403 Check the App's permission for the operation. The admin creation setting governs creating Apps in settings, not API access for existing Apps.
404 Check the identifier, grants and owner's current access.
409 Resolve a version/idempotency conflict or retrieve current Required Context.
415 Send the write as JSON.
422 Correct the named validation fields.
429 Respect Retry-After before retrying.

For example, a create request without a title returns HTTP 422 with a field-specific error:

{
  "message": "The title field is required.",
  "errors": {
    "title": ["The title field is required."]
  }
}

Add --include to a cURL command to inspect the HTTP status and headers, including Idempotency-Replayed and Retry-After. Read the error body to distinguish a stale version from an idempotency conflict or missing Required Context before retrying a 409.

Limits are 120 requests per minute per credential and 240 per minute per IP. Keep credentials on your server, out of browser code, work items and shared documents.

Submit and read Signals

Signals requires Starter or above. Existing Apps do not receive signals:write or signals:read automatically; grant each explicitly. These permissions apply to shared workspace reports. Suggestions, linked work, and project-derived history additionally respect the App's project grants and its owner's current access.

Send POST /signals with client_submission_id, body, source_key, and source_label. Optional fields are signal_id to append evidence, external_event_id, an HTTP(S) source_url, occurred_at, reported_count, window_start, window_end, a metadata object, and project_ids for starting project associations. Send both reporting-window bounds together. Text is limited to 60,000 characters and metadata to 16 KB.

{
  "client_submission_id": "accounts-monitor-event-42",
  "body": "Account login requests return HTTP 503.",
  "source_key": "accounts-monitor",
  "source_label": "Accounts monitor",
  "external_event_id": "event-42",
  "reported_count": 12,
  "window_start": "2026-09-27T10:00:00Z",
  "window_end": "2026-09-27T10:05:00Z"
}

project_ids accepts up to 20 distinct active project ULIDs, each within both the App's project grants and its owner's current access. Signals write permission alone does not grant access to project routing. These hints accompany the original report and can coexist with later Looper associations; they never create work. Reordering the same IDs is safe on retries, but changing them under the same submission or event identity returns a conflict. Read responses expose current project_hints and each report's original project_hints only within current project access. The project_key filter also finds raw signals associated with that project before any proposal exists.

Keep client_submission_id stable for delivery retries. Source/event identities also deduplicate reports across intake surfaces. Reusing an identity with different evidence returns a conflict. A submission-only App receives a minimal receipt containing signal and report identifiers and whether the report was a duplicate; it receives no shared evidence or suggestion bodies.

With signals:read, use GET /signals and GET /signals/{id}. The Signals list accepts q, state, source, project_key, cursor, and limit (maximum 50). Follow its returned cursor with the same filters. The App API cannot accept suggestions, discard signals, change their workflow, or request processing. Members perform those actions through the web, MCP, or Looper confirmation cards. See the Signals guide for the lifecycle and subscription cadence.