تخطَّ إلى المحتوى

Integration Cookbook

هذا المحتوى غير متوفر بلغتك بعد.

Integration Cookbook

Complete, copy-pasteable journeys — not per-endpoint fragments. Every recipe assumes BASE=https://ghostmind.optdmsa.com and a bearer key in $GHOSTMIND_API_KEY. Errors use the canonical envelope ({ "error": { "code", "message", "request_id" } }) — see Handling Errors.


Recipe 1 — Project-scoped chat with file retrieval

The flagship flow: a project with instructions + source files, then conversations that actually retrieve file content upstream.

Terminal window
# 1) Create the upstream project (instructions included)
PROJECT=$(curl -sS -X POST "$BASE/v1/projects" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "RetentionOS Engine",
"instructions": "You are the reply engine. Always answer in JSON.",
"memory_policy": "project_only"
}')
SLUG=$(echo "$PROJECT" | jq -r .slug) # e.g. "retentionos-engine"
PROJ_ID=$(echo "$PROJECT" | jq -r .id) # GhostMind UUID
# 2) Upload a source file (multipart, max 10 MB)
FILE=$(curl -sS -X POST "$BASE/v1/projects/$PROJ_ID/files" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-F "file=@brand_voice.txt;type=text/plain")
FILE_ID=$(echo "$FILE" | jq -r .id)
# 3) Poll until the file is indexed ("ready" — retrieval needs indexing,
# not just upload)
while :; do
ST=$(curl -sS "$BASE/v1/projects/$PROJ_ID" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
| jq -r '.sources[] | select(.id=="'"$FILE_ID"'") | .status')
[ "$ST" = "ready" ] && break
[ "$ST" = "failed" ] && { echo "file indexing failed"; exit 1; }
sleep 2
done
# 4) Chat inside the project — the SLUG goes in X-GhostMind-Upstream-Project
curl -sS -X POST "$BASE/v1/chat/completions" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-H "X-GhostMind-Upstream-Project: $SLUG" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Summarize the brand voice file in one line."}]}'

Gotchas that bite real integrators:

  • X-GhostMind-Upstream-Project takes the slug, not the UUID. Passing a UUID (or a wrong slug) returns 404 upstream_project_not_found. Passing a slug via X-GhostMind-Project (the internal header) returns 422 wrong_project_header.
  • A file reporting status: "uploading"/"processing" is not retrievable yet — chat before "ready" silently ignores the source.
  • Project file upload can return 403 UPSTREAM_ACCOUNT_NOT_AUTHORIZED: the connected ChatGPT account’s tier does not permit project file attachment. Check capabilities.project_files.enabled before building a flow around project files, and fall back to Recipe 5 conversation attachments (a different capability that works on all account tiers).
  • memory_policy: "project_only" keeps project memory from leaking into the account’s general memory — recommended for multi-tenant workloads.

Recipe 2 — Durable conversation (send now, continue anytime)

Terminal window
# First message — capture the continuity header from the RESPONSE
RESP=$(curl -siS -X POST "$BASE/v1/chat/completions" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Remember: the invoice prefix is RT-."}]}')
CONV=$(echo "$RESP" | grep -i '^x-ghostmind-conversation-id:' | tr -d '\r' | awk '{print $2}')
# Continue later (minutes or days) — same upstream conversation, same
# project binding. Continuity is sticky to the original account.
curl -sS -X POST "$BASE/v1/chat/completions" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-H "X-GhostMind-Conversation-Id: $CONV" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"What is the invoice prefix?"}]}'

Continuity failure codes are explicit: 410 conversation_deleted_upstream, 419 conversation_session_expired, 409 conversation_continuity_lost, 503 conversation_upstream_unavailable. None silently start a new chat.

Recipe 3 — Async speech generation (queue + poll + download)

Speech generation takes ~30–120 s upstream — always use the async job path, not the bounded sync endpoint, for anything non-trivial.

Voice names are provider-specific Arabic names (e.g. يوسف, مالك, ليلى) — there is no savio_default. List them first:

Terminal window
curl -sS "$BASE/v1/audio/providers" -H "Authorization: Bearer $GHOSTMIND_API_KEY"
curl -sS "$BASE/v1/audio/providers/google_ai_studio_savio_tts/voices" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY"
Terminal window
# Enqueue — returns 202 immediately
JOB=$(curl -sS -X POST "$BASE/v1/audio/generations" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "مرحباً بك في منصتنا",
"voice": "يوسف",
"response_format": "wav"
}')
JOB_ID=$(echo "$JOB" | jq -r .id)
# Poll — status: created → queued → processing → completed | failed
while :; do
ST=$(curl -sS "$BASE/v1/audio/generations/$JOB_ID" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" | jq -r .status)
case "$ST" in
completed) break ;;
failed|cancelled) echo "job $ST"; exit 1 ;;
esac
sleep 5
done
# Download — content expires after 24h by default
# (operator-configurable via GHOSTMIND_AUDIO_JOB_TTL_HOURS)
curl -sS "$BASE/v1/audio/generations/$JOB_ID/content" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" -o clip.wav

Recipe 4 — Transcription

Terminal window
curl -sS -X POST "$BASE/v1/audio/transcriptions" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-F "file=@clip.wav" -F "model=whisper-1"
# → {"text": "..."} (usually < 2 s for short clips)

Recipe 5 — Conversation attachments (send files/images to chat)

Two steps, and both are required. Uploading stores the file and returns a file_id, but the file is only visible to the model when you reference that id in attachment_file_ids on the chat request. Uploading alone does not attach it to the next message.

Terminal window
# 1) Upload to an existing conversation — capture file_id
ATT=$(curl -sS -X POST "$BASE/v1/conversations/$CONV/attachments" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-F "file=@invoice.pdf")
FILE_ID=$(echo "$ATT" | jq -r .file_id) # e.g. file_00000000...
# 2) Reference it in the chat request body
curl -sS -X POST "$BASE/v1/chat/completions" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-H "X-GhostMind-Conversation-Id: $CONV" \
-H "Content-Type: application/json" \
-d "{\"model\":\"gpt-4o\",\"attachment_file_ids\":[\"$FILE_ID\"],
\"messages\":[{\"role\":\"user\",\"content\":\"Summarize the attached invoice.\"}]}"

Notes:

  • file_ids are per upstream account — an id uploaded under one account cannot be used in a conversation bound to another (400 invalid_attachment).
  • Image attachments must have stored dimensions or are rejected 422.
  • Conversations created via POST /v1/conversations already carry an upstream account binding, so attachments work immediately. A 409 Conversation has no upstream account binding only occurs for records created through other paths that left the binding null.

Recipe 6 — Usage reconciliation

Terminal window
curl -sS "$BASE/v1/usage?period=7d" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY"
# → totals + per-model + per-day buckets, scoped to THIS key only.

Use it for quota dashboards and billing reconciliation — it never exposes other tenants or provider internals.

Recipe 7 — Cleanup discipline

GhostMind deletes are soft deletes (conversations restorable via POST /v1/conversations/{id}/restore). For task-scoped conversations:

Terminal window
curl -sS -X DELETE "$BASE/v1/conversations/$CONV" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY"

Hard purges are an operator decision, not an API self-service — see Deletion and Retention.

Recipe 8 — Run a UAC Action with a caller-supplied file

Published Actions whose inputs include a file field take an asset_id — never inline bytes. Upload once, reference it in the run, poll for the verdict.

Terminal window
# 1) Upload the file against the action (multipart `file` field,
# actions:run scope required; default cap 20 MiB, TTL 24h)
ASSET=$(curl -sS -X POST "$BASE/v1/actions/$ACTION_SLUG/assets" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-F "file=@photo.png;type=image/png")
ASSET_ID=$(echo "$ASSET" | jq -r .asset_id)
# 2) Create the run — the file input is {"asset_id": "..."}
RUN=$(curl -sS -X POST "$BASE/v1/actions/$ACTION_SLUG/runs" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": {"photo": {"asset_id": "'"$ASSET_ID"'"},
"caption": "launch day"}}')
RUN_ID=$(echo "$RUN" | jq -r .id)
# 3) Poll — 202 means ACCEPTED, not succeeded. The verdict is the truth.
while :; do
R=$(curl -sS "$BASE/v1/actions/$ACTION_SLUG/runs/$RUN_ID" \
-H "Authorization: Bearer $GHOSTMIND_API_KEY")
S=$(echo "$R" | jq -r .status)
[ "$S" = "queued" ] || [ "$S" = "running" ] || break
sleep 2
done
echo "$R" | jq '{status, verdict, error_code}'

Semantics that matter:

  • An asset is single-use — a second run with the same asset_id fails validation. Upload again for each run.
  • SUCCEEDED means the outcome oracle confirmed the effect; needs_review / UNKNOWN are not success — inspect the run’s oracle evidence before treating it as done.
  • Assets are tenant- and action-scoped: an asset_id uploaded under a different action or tenant is rejected at run creation.

Choosing the right header — the 30-second table

HeaderTakesWhen
X-GhostMind-Conversation-Idconversation UUIDcontinue a conversation
X-GhostMind-Upstream-Projectproject slugchat inside an upstream project
X-GhostMind-Upstream-Accountaccount labelpin a specific connected account
X-GhostMind-Route-Profileprofile slugnamed routing policy
X-GhostMind-Projectinternal projectplatform-internal bindings — most integrators never need it
X-Request-Idany stringcorrelation through errors/logs