Tracira

Any HTTP client

Tracira works with any tool that can make an HTTP POST request, including Zapier, Pipedream, custom scripts, or your own server.

Tracira works with any tool that can make an HTTP POST request: Zapier, Pipedream, custom scripts, or your own server. Here is the complete request format.

Prefer to click than to read?

Import the Postman collection and run the calls on this page against your own workspace. It covers sending an output, labelling its files, and editing it afterwards.

Get your API token

Open the Integrations page in your workspace and copy your token from there.

Send the request

POST to https://tracira.com/api/logs with:

  • Header: Authorization: Bearer YOUR_TOKEN
  • Body: JSON with at minimum project and output

Use the verdict

Pass "sync": true to receive the evaluation result immediately in the response. Check status: pass means nothing needs a person, flagged means the output waits on the Review page, and error means the check could not finish. verdict (pass, fail or unsure) and explanation say why.

Send an output

curl -X POST https://tracira.com/api/logs \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project": "my-project",
    "output": "The AI-generated text.",
    "sync": true
  }'

Response

{
  "ok": true,
  "id": "b1c2d3e4-...",
  "status": "pass",
  "verdict": "pass",
  "confidenceScore": 0.95,
  "explanation": "The reply stays within the 20% discount cap."
}

Without sync, the call returns HTTP 202 straight away with { "ok": true, "id": "...", "status": "pending" } and the verdict follows in the background.

Logging a conversation

For a chatbot or any multi-turn thread, send one output per exchange and group the turns with sessionId - don't send the full messages array on every call:

{
  "project": "Customer Support",
  "input": "Hi, my order #8841 never arrived. Can you help?",
  "output": "Sorry to hear that! I've checked order #8841 and...",
  "sessionId": "thread_5f2a"
}
  • input is only the newest user message - the one this reply answers. Your call to your AI provider can still carry the full history; Tracira only needs the new turn.
  • sessionId is your conversation or thread ID, identical on every turn. Tracira stitches all outputs sharing it into one readable thread, nothing repeated. (conversationId, threadId, and chatId are accepted as aliases.)
  • Leave the system prompt out - it's configuration, not conversation. The best place for it is Tracira itself - see Hosted instructions.

Hosted instructions (self-improving prompts)

Tracira can host the instructions (system prompt) your AI runs with, versioned, with the people who review its output able to read, edit, and restore them in the dashboard. Fetch them at the start of every run instead of hardcoding the prompt:

curl -X POST https://tracira.com/api/instructions \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project": "Customer Support",
    "task": "Email reply",
    "default": "You write friendly, concise replies to customer emails..."
  }'
# -> { "content": "You write friendly...", "version": 3, "updatedAt": "..." }

The very first call saves default as version 1 and returns it; after that, the stored text always wins and default is ignored. Use content as your system message, and include the version when you submit the output so it links back to the exact instructions that produced it:

{
  "project": "Customer Support",
  "task": "Email reply",
  "input": "Where is my order?",
  "output": "It ships tomorrow...",
  "instructionsVersion": 3
}

To make the prompt self-improving, react to a changed (edited) decision: rewrite the current instructions with your own AI step so the reviewer's comment is followed from now on, then save the result as the new active version:

curl -X POST https://tracira.com/api/instructions/versions \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project": "Customer Support",
    "task": "Email reply",
    "content": "You write friendly, concise replies... Always sign off as The Support Team.",
    "teachComment": "Always sign off as The Support Team",
    "logId": "b1c2d3e4-..."
  }'
# -> { "version": 4 }

Tracira stores, versions, and serves the text - the rewriting stays in your automation, with your own AI step and keys. See the API reference for the full schemas.

Reviewers can also coach replies that already went out: pressing Teach the AI on a decided or passed output fires a taught webhook event. It carries the same fields as a changed decision (comment, output, instructionsVersion, ...) but nothing should be regenerated or resent - route it to the same instructions-rewrite step so the lesson applies from the next run onward.

Reviewing an action before it runs

When your AI decides to do something with side effects (issue a refund, delete a record, send an escalation), you can hold that action for human approval. Two fields do two different jobs, and you usually want both:

  • action describes what is being reviewed. Tracira shows reviewers the plain-language summary so they know exactly what they are approving.
  • requireApproval decides whether a person is asked at all. Set it to true and the output waits in the review queue no matter what your rules conclude.
{
  "project": "Customer Support",
  "output": "I have prepared the refund and sent it for approval.",
  "action": {
    "name": "issue_refund",
    "summary": "Refund €49.00 to Alice Martin (order #8841)",
    "params": { "amount": 49.0, "currency": "EUR", "order": "8841" }
  },
  "requireApproval": true,
  "callbackUrl": "https://hook.eu1.make.com/abc123"
}

An action on its own does not stop anything

action is a description, not a gate. Send it without requireApproval and your rules alone decide: in a workspace with no rule that matches this project and task, the output passes and your automation runs the refund unreviewed. If a person must sign off, say so with requireApproval.

Your automation supplies the summary - write it as a clear sentence, because reviewers read it verbatim to decide. Tracira never executes the action itself.

Rules still run either way, and they are the right tool when only some actions need a person: a data-field rule on action.params.amount flags refunds over a threshold and lets smaller ones through. When a rule flags the output, its explanation is what the reviewer reads; requireApproval only adds the reviewer, it never overwrites what your rules found. Nothing else about your payload changes - both fields are optional and additive.

Attaching files

Tracira can store images, audio, and PDFs alongside an output so reviewers see the source the AI worked from. How you attach depends on the file size. For a file Tracira already holds, see Getting a file back out instead: re-attach it by key rather than sending the bytes a second time.

The 4.5 MB request-body limit

The /api/logs request body is capped at 4.5 MB by our hosting platform. Because base64 inflates a file by about 33%, a base64-inline file effectively has to stay under ~3 MB. Larger files return 413 FUNCTION_PAYLOAD_TOO_LARGE. For anything bigger, use a URL or a presigned upload (below), where the file never travels in the request body.

Small files: URL or inline base64

{
  "project": "Invoice Review",
  "output": "...",
  "attachments": [
    { "source": "url", "url": "https://example.com/invoice.pdf" }
  ]
}

source: "url" (Tracira fetches it server-side, up to 32 MB) or source: "upload" with base64 data (kept under the 4.5 MB body cap).

Say what each file is

When an output carries more than one file, add a label (max 120 characters) so nobody has to work out which is which from IMG_0042.jpg:

"attachments": [
  { "source": "url", "url": "https://example.com/a.jpg", "label": "Before photo" },
  { "source": "url", "url": "https://example.com/b.jpg", "label": "After photo" }
]

The label sits above the file in the reviewer's transcript instead of the generic word "Image", and comes back on every attachments entry your automation receives, so you can pick out the one you need without matching filenames. outputAttachments takes it too, for files the AI produced.

When to set it

The label belongs to the file's role in this output, so it is set when the file is attached, never when it is uploaded:

MomentWhere the label goes
Uploading a large file (POST /api/uploads)Nowhere. A staging upload is bytes with no output yet, so there is nothing to describe.
Attaching it to an output (POST /api/logs)attachments[].label. This is the moment, and it is the same field whichever source you used: url, inline data, an uploaded key, or a stored one.
Re-attaching a stored file to a revisionCarried over automatically, the same way the filename is. Send a label only to change it.
After the output existsPATCH /api/logs/{id}.

So the two-step upload flow below sets the label on the second call, alongside the key:

{ "attachments": [{ "source": "uploaded", "key": "KEY_FROM_STEP_1", "label": "Signed contract" }] }

Large files: direct upload (up to 32 MB)

Upload the file straight to Tracira's storage, then reference it by key. Three calls:

Create the upload

curl -X POST https://tracira.com/api/uploads \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "invoice.pdf" }'
# -> { "uploadUrl": "https://...", "key": "...", "contentType": "application/pdf" }

Upload the bytes

PUT the raw file to uploadUrl with the Content-Type from the response. These bytes go straight to storage, not through /api/logs, so they are not subject to the 4.5 MB cap:

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --data-binary @invoice.pdf

Submit the output

Reference the upload by key. Nothing else is needed; Tracira links the file to this output:

curl -X POST https://tracira.com/api/logs \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project": "Invoice Review",
    "output": "The invoice looks valid.",
    "attachments": [{ "source": "uploaded", "key": "KEY_FROM_STEP_1", "label": "Signed contract" }]
  }'

Quota and cleanup

Each in-flight upload reserves space against your workspace storage quota (the sizeBytes you declare, or the per-file max if omitted), so pending uploads can never push you over your plan limit. The reservation is released when the upload is linked to an output, and any upload never referenced by an output is deleted automatically within 24h. Linked files count toward storage like any other attachment.

Tip

For Zapier or Pipedream: use their built-in HTTP/Webhook action, set Method to POST, add the Authorization header, and paste the JSON body. Then add a conditional step after it to branch on status. For files over ~3 MB, do the create-upload + PUT as two HTTP steps before the final POST.

Updating an output

Context often arrives after the output does: your CRM assigns a ticket number a second later, a classifier tags the conversation on the next step, a file's role only becomes clear once a second file lands beside it. PATCH /api/logs/{id} writes that context onto an output already in Tracira.

curl -X PATCH https://tracira.com/api/logs/LOG_ID \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "metadata": { "ticket_id": "1042", "crm_stage": "escalated" },
    "subjectId": "ticket_1042",
    "attachments": [
      { "key": "WORKSPACE_ID/LOG_ID/0-ab12cd34-before.jpg", "label": "Before photo" },
      { "key": "WORKSPACE_ID/LOG_ID/1-ef56gh78-after.jpg",  "label": "After photo" }
    ]
  }'
  • metadata merges. Only the keys you send change. A key whose value is a blank string is skipped, the same way an empty value is dropped when you send an output, so a field that is sometimes blank can never wipe what is stored. Pass "metadataMode": "replace" to store exactly what you send and drop the rest; replace with no metadata at all (or "metadata": {}) clears everything, while merge with no metadata changes nothing.
  • Structured values go in metadataJson. metadata accepts nested objects and arrays directly if you are writing JSON by hand. But Make and n8n build metadata from a key-value list whose values are strings, so JSON typed into a row is stored as text. "metadataJson": { "invoice": { "total": 1245.5 } } carries it intact from any client, and a metadata row wins if both name the same key.
  • Deleting a key is explicit, with two spellings for two kinds of caller: set the key to null if you are writing JSON, or list its name in "metadataRemove": ["crm_stage"] if your key-value builder can only emit strings. Unknown names are ignored.
  • attachments relabels, it never adds. Address each file by the key (or url) from the output's own attachments array, and give it a label (null clears it). To add a file, send a new output.
  • Context IDs can be backfilled with sessionId, subjectId, actorId or any of their aliases (ticketId, conversationId, endUserId, …), top-level or inside metadata - the same spellings the ingest endpoint accepts.

What cannot be edited

Input, output, verdict and the human decision are immutable. They are the record a person judged, and an audit trail you can rewrite is not an audit trail. Editing metadata also does not re-run your rules: the verdict belongs to the output that was judged. If the AI produced something new, send it as a new output (with revisionOf to link the two).

Editing metadata returns 409 LOG_PENDING while the output is still being evaluated, because that evaluation rewrites the metadata when it lands. Labels and context IDs can be set at any time.

Getting a file back out

Every payload that describes an output carries an attachments array: the webhook events, the per-output callbackUrl, and GET /api/logs/{id}. Each entry names one stored file:

"attachments": [
  {
    "role": "input",
    "type": "file",
    "filename": "invoice-april.pdf",
    "contentType": "application/pdf",
    "key": "WORKSPACE_ID/LOG_ID/0-ab12cd34-invoice-april.pdf",
    "url": "https://tracira.com/api/media/WORKSPACE_ID/LOG_ID/0-ab12cd34-invoice-april.pdf",
    "label": "April invoice"
  }
]

role is input when the AI received the file and output when it produced one. key and url name the same object; use whichever your tool maps more easily. label is what you called the file when you sent it, or null if you did not name it - that is the field to branch on when one output carries three images and you only want the signed one.

This same array, label included, is on every payload Tracira sends you: the webhook subscription events (evaluation, decision, teaching), the per-output callbackUrl, and GET /api/logs/{id}. A workflow handling a "redo this" decision reads attachments[].label to find the document it needs.

This is what makes a redo possible for document work. When a reviewer sends a parsed PDF back with a comment, your automation has long since finished and no longer holds the file. Ask Tracira for it instead.

Download the file

Exchange the key or URL for a short-lived signed link, then fetch it:

curl -G https://tracira.com/api/media-url \
  -H "Authorization: Bearer YOUR_TOKEN" \
  --data-urlencode "source=WORKSPACE_ID/LOG_ID/0-ab12cd34-invoice-april.pdf"
# -> { "url": "https://...", "expiresAt": "...", "key": "...", "filename": "...", "contentType": "..." }

curl "$SIGNED_URL" -o invoice-april.pdf

Do not send your token to the signed URL

The signed link carries its own signature in the query string and rejects any request that also carries an Authorization header. Most HTTP clients reuse headers across calls, so send the token to /api/media-url only.

The same trap applies to GET /api/media/{key}, which redirects to that signed link: a client that follows redirects will carry your header along and get a 403 from storage. Use /api/media-url for automations and save the redirect route for browsers.

Keep the file on the new version

When you resubmit the corrected output, re-attach the same file by key instead of uploading it again:

curl -X POST https://tracira.com/api/logs \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project": "Invoice Review",
    "output": "{\"total\": 1245.00}",
    "revisionOf": "LOG_ID",
    "attachments": [{ "source": "stored", "key": "WORKSPACE_ID/LOG_ID/0-ab12cd34-invoice-april.pdf" }]
  }'

The url works here too: paste it in place of the key and Tracira reads the key out of it.

Each version keeps its own copy

A re-attached file is copied into the new output rather than shared with the old one. That is deliberate: deleting an output deletes its files, so a shared file would vanish from the revision the moment someone cleaned up the original. It does mean a document revised three times is stored four times and counts four times toward your storage quota.

If the original output has already been deleted or has passed your activity history limit, the re-attach fails with a 422 saying the file is no longer stored. Send the file itself in that case.

On this page