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
projectandoutput
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"
}inputis 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.sessionIdis your conversation or thread ID, identical on every turn. Tracira stitches all outputs sharing it into one readable thread, nothing repeated. (conversationId,threadId, andchatIdare 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:
actiondescribes what is being reviewed. Tracira shows reviewers the plain-languagesummaryso they know exactly what they are approving.requireApprovaldecides whether a person is asked at all. Set it totrueand 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:
| Moment | Where 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 revision | Carried over automatically, the same way the filename is. Send a label only to change it. |
| After the output exists | PATCH /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.pdfSubmit 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" }
]
}'metadatamerges. 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;replacewith no metadata at all (or"metadata": {}) clears everything, whilemergewith no metadata changes nothing.- Structured values go in
metadataJson.metadataaccepts nested objects and arrays directly if you are writing JSON by hand. But Make and n8n buildmetadatafrom 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 ametadatarow wins if both name the same key. - Deleting a key is explicit, with two spellings for two kinds of caller: set the key to
nullif 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. attachmentsrelabels, it never adds. Address each file by thekey(orurl) from the output's own attachments array, and give it alabel(nullclears it). To add a file, send a new output.- Context IDs can be backfilled with
sessionId,subjectId,actorIdor any of their aliases (ticketId,conversationId,endUserId, …), top-level or insidemetadata- 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.pdfDo 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.