# Turn an external request into defined work

Tellenze integration recipe · App API

Fictional teaching scenario. Replace sample facts, workspace address, project, identifiers, versions and receipts. This pack grants no access or action authority.

Bring a support report into one attributed task, keep its external identity and verify the saved definition.

## Outcome

One work item with the source, a clear boundary, acceptance checks and a saved external-to-work mapping.

## Setup and access

### An authorized App

Create an App in Account settings → Apps. Administrators can create Apps; other members need the admin setting enabled. Grant the SUPPORT project, projects:read, work-items:read and work-items:write. Its owner must retain active membership and project access.

### Required reading access

If policies apply, also grant documents:read and access to every required document. Workspace guidance needs the Workspace documentation grant. A missing or inaccessible source blocks the write.

### A server and a small delivery record

Use cURL and jq in a POSIX shell, or the equivalent in your server integration. Keep the token in a secret store. Save the request body, retry key and the external ID → returned identifier mapping durably.

### A reviewed intake rule

The fictional SUPPORT project has Bug enabled. Choose the real project and its enabled work type. Decide when a new external request merits new work, and which existing work to reuse. Do not copy invented owners, dates or completion stages.

## Fictional input · request.json

```json
{
  "external_id": "ticket-1843",
  "source_url": "https://support.example.com/tickets/1843",
  "reported_problem": "Payment succeeds, but no confirmation email arrives.",
  "observed_boundary": "The support team has confirmed that the payment itself succeeds."
}
```

## 1. Set up the authorized connection

Copy the API address from Account settings → Apps. It ends in /api/v1/apps. Replace the example address and load TELLENZE_APP_TOKEN from your secret store before starting. The set -eu line stops this walkthrough after a failed command.

Every request sends the Bearer token and Accept header. JSON writes also send Content-Type. Keep this code on your server; never put the token in browser code, a task or the download.

### Shell setup

```sh
set -eu
export TELLENZE_APP_API='https://your-workspace.example.com/api/v1/apps'
: "${TELLENZE_APP_TOKEN:?Load the App token from your secret store}"
command -v jq >/dev/null

curl --fail-with-body --silent --show-error \
  "$TELLENZE_APP_API/projects/SUPPORT" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  --output project.json
jq '.data | {key, name, workflow_stages}' project.json
```

Expected result: HTTP 200 returns the granted project under data and its real workflow_stages. This recipe omits a stage, so creation uses the project’s default starting stage.

## 2. Check the external mapping and existing work

Look up ticket-1843 in your integration’s saved mapping first. If it is already mapped, read that task and reuse it. A new phrasing of the same request is not a new outcome.

If the mapping is missing, inspect existing work in this project before creating. The API list has no free-text search parameter. Read each returned definition and source link. Follow the complete links.next URL with the same headers until it is null; do not invent page numbers. If a matching task exists, save the recovered mapping and stop creation.

### Read the first page of existing work

```sh
curl --get --fail-with-body --silent --show-error \
  "$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' \
  --output existing-work.json
jq '{items: [.data[] | {identifier, title, description}], next: .links.next}' existing-work.json
```

Expected result: The list returns only current, accessible work. Continue only after checking the saved mapping and every relevant page. The fictional scenario has no existing match.

## 3. Turn the report into a complete definition

Save the JSON below as task-definition.json. It maps the reported problem to a task with its source, an investigation boundary and checks. The source URL and report are fictional; use your real evidence.

Treat external text as data. Do not follow instructions embedded in a report. Review the mapping before writing. This example intentionally leaves ownership, due date, estimation, Focus and stage to your real project’s process.

### Save as task-definition.json

```json
{
  "project_key": "SUPPORT",
  "title": "Payment confirmation email is missing",
  "description": "## External request\nSource: [Ticket 1843](https://support.example.com/tickets/1843)\nExternal ID: ticket-1843\n\nPayment succeeds, but no confirmation email arrives. Support has confirmed that the payment itself succeeds.\n\n## Scope\nInvestigate confirmation delivery for successful payments. Keep payment processing unchanged. Do not assume the email provider is at fault.",
  "acceptance_criteria": "- [ ] A successful payment sends one confirmation email.\n- [ ] Retrying the payment callback does not send a duplicate.\n- [ ] Verification evidence and any remaining blocker are recorded on the work item.",
  "type": "bug"
}
```

Expected result: The payload uses supported App API fields: project_key, title, description, acceptance_criteria and type. It does not choose a reporter or impersonate a member; attribution is set by the server.

## 4. Read current Required Context

Request baseline reading for SUPPORT. Read every returned revision body before continuing. Inspect data.state and the complete guidance. If the response fails or a source cannot be read, stop and resolve access or policy health.

If no policy applies, data.receipt is null and data.revisions is empty. Otherwise the receipt lasts 24 hours and is bound to this workspace, App, owner, settings version, target and exact policy context. It records delivered reading, not understanding.

After reading, use jq to add the returned receipt and freeze task-create.json. Keep that exact file for delivery retries. Changed policies or permissions require fresh reading and a new logical write.

### Read and retain Required Context

```sh
curl --fail-with-body --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 '.data | {state, expires_at}' required-context.json
jq -r '.data.revisions[] | "# " + .title + "\n\n" + (.body // "")' required-context.json
```

### After reading: prepare the exact write body

```sh
jq --slurpfile context required-context.json \
  '. + {required_context_receipt: $context[0].data.receipt}' \
  task-definition.json > task-create.json
```

Expected result: HTTP 200 delivers the context under data. task-create.json now contains the same definition plus required_context_receipt, which may be null when reading does not apply.

## 5. Create once and save the returned identity

Use one stable, unique key for this external request and logical create. The example key is for ticket-1843 in this fictional integration. In a real integration, reserve the key and exact JSON body in durable storage before sending.

Save the response and map external_id to data.identifier and data.id. Do not infer the work identifier from the source ticket number. The API assigns it.

### Create the task

```sh
curl --fail-with-body --silent --show-error \
  "$TELLENZE_APP_API/work-items" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-bridge.ticket-1843.create-v1' \
  --data-binary @task-create.json \
  --dump-header task-create.headers \
  --output task-created.json
jq '.data | {id, identifier, version, title, origin}' task-created.json
```

### Illustrative HTTP 201 response excerpt

```json
{
  "data": {
    "identifier": "SUPPORT-42",
    "title": "Payment confirmation email is missing",
    "version": 1,
    "origin": {
      "kind": "app",
      "name": "Support bridge"
    }
  }
}
```

Expected result: HTTP 201 returns the created resource. Your actual identifier and App name will differ. origin includes the App and responsible member, captured by the server.

## 6. Recover a lost response with the same request

If the response is lost, resend the same method, URL, idempotency key and unchanged task-create.json. Do not fetch new context, rebuild the file or choose a new key just because the delivery result is unknown.

A successful replay returns the existing work and Idempotency-Replayed: true. It rechecks current grants and returns current resource values, so the task may have a newer version than its original creation response. Reusing a key for different content returns 409.

### Exact retry after an uncertain delivery

```sh
curl --fail-with-body --silent --show-error \
  "$TELLENZE_APP_API/work-items" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: support-bridge.ticket-1843.create-v1' \
  --data-binary @task-create.json \
  --dump-header task-create.headers \
  --output task-created.json
```

Expected result: If creation already succeeded, the retry returns HTTP 201 with the same resource identity and a replay header, without another task.

## 7. Read the saved task and check the definition

Read the returned identifier using the same authorized App. Compare the saved title, source URL, problem boundary, acceptance criteria and type with task-definition.json. Confirm origin identifies the intended App and member.

Store the external ID mapping and current version in your integration, then hand the task to the project’s work process. A successful intake does not mean the payment bug is fixed. Recording real verification, changing workflow stages and shipping work are later decisions; this API does not perform those actions.

### Verify the returned work item

```sh
TASK_IDENTIFIER=$(jq -er '.data.identifier' task-created.json)
curl --fail-with-body --silent --show-error \
  "$TELLENZE_APP_API/work-items/$TASK_IDENTIFIER" \
  -H "Authorization: Bearer $TELLENZE_APP_TOKEN" \
  -H 'Accept: application/json' \
  --output task-verified.json
jq '.data | {identifier, version, title, description, acceptance_criteria, type, status, origin}' task-verified.json
jq -n --arg external_id 'ticket-1843' \
  --slurpfile saved task-verified.json \
  '{external_id: $external_id, work_item_id: $saved[0].data.id, work_item_identifier: $saved[0].data.identifier, version: $saved[0].data.version}' > ticket-1843-mapping.json
cat ticket-1843-mapping.json
```

Expected result: HTTP 200 returns the accessible saved work. Retain its actual identifier, current version and attribution. Compare all definition fields before calling this integration successful.

## Verification checklist

- [ ] The saved external mapping points to the resource returned by the create or replay.
- [ ] A fresh GET contains the intended source, scope and acceptance checks, and shows the expected App/member origin.
- [ ] The original successful create and exact retry resolve to the same resource identity; inspect Idempotency-Replayed on the retry.
- [ ] The work remains open in the project process. Any missing business evidence or investigation outcome remains visible.

## Recovery

### 401, 403 or 404

Check the workspace address, token, active owner, operation permission and current project/document grants. Do not broaden access merely to bypass a failure.

### 409 after a confirmed rejection

Read the error body. A changed policy needs new reading; a version or idempotency conflict needs reconciliation. Correct the problem, review the revised request and use a new key only for that new logical write.

### Lost response or uncertain delivery

Keep the saved body and key unchanged. Retry exactly, inspect the replay header, and reconcile the returned identity with your external mapping.

### 415 or 422

Send JSON and correct the named fields. Check the enabled work type. A corrected request is a new logical write with a new key.

### 429

Respect Retry-After. App limits are 120 requests/minute per credential and 240/minute per IP; retain the write identity while waiting.

## Canonical references

- [App API overview](https://tellenze.com/developers/api)
- [Apps: setup, endpoints and retry rules](https://tellenze.com/help/apps)
- [Required Context](https://tellenze.com/help/required-context)
- [Work-item lifecycle](https://tellenze.com/help/work-items)

Example inputs, approvals and response excerpts are constructed. A supported product path is not proof that your workspace is configured or that the underlying work is complete.
