escalio Docs

ORGANIZATION API · V1

Create a ticket.

Send the result of your existing job to your team's queue. Keep your scheduling and business logic where they are.

POSThttps://YOUR-WORKSPACE.escalio.org/api/v1/tickets
What this creates

An open, unassigned ticket with an internal note. The API itself does not send an email. Your configured ticket-created automations may send notifications. This key cannot read or update tickets, upload files, select an assignee, set custom fields or send customer replies.

1. Create a key

In your organization's workspace, open your avatar → API keys. Owners and admins can also use Settings → Team → API keys. Active agents can create and revoke their own keys; owners/admins manage all organization keys. Collaborators cannot create them.

Give the key a name and an integration/project label, choose its expiry, and copy it into your job's secret storage. The full key is shown only once. Its permission is tickets:create. Disabling its creator does not revoke it; revoke or replace it explicitly when needed.

Use the organization's workspace address, not escalio.org or a custom customer-portal domain. Keep the key on your server; do not put it in browser code, a URL or a chat.

2. Send a request

Set ESCALIO_ORIGIN to your workspace origin and supply ESCALIO_API_KEY from your secret store. Use a stable batch ID for ESCALIO_REQUEST_ID. Save the exact payload as ticket.json before sending it.

These examples use the same payload and headers. cURL uses a POSIX shell; Python uses its standard library; JavaScript runs in Node.js with built-in fetch.

ticket.json

{
  "subject": "Released fixes to follow up",
  "body": "Released fix: https://issues.example.com/APP-123\nCustomer follow-up: https://tasks.example.com/task-456",
  "priority": "normal",
  "tags": [
    "release_followup"
  ]
}

Environment (POSIX shell)

export ESCALIO_ORIGIN='https://YOUR-WORKSPACE.escalio.org'
export ESCALIO_REQUEST_ID='release-followup-batch-001'
# Supply ESCALIO_API_KEY from your job's secret store; do not commit it.

cURL (POSIX shell)

curl --fail-with-body --show-error --max-time 30 \
  --request POST "$ESCALIO_ORIGIN/api/v1/tickets" \
  --header "Authorization: Bearer $ESCALIO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: $ESCALIO_REQUEST_ID" \
  --data-binary @ticket.json

Python 3

import json
import os
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen

request = Request(
    os.environ["ESCALIO_ORIGIN"].rstrip("/") + "/api/v1/tickets",
    data=Path("ticket.json").read_bytes(),
    method="POST",
    headers={
        "Authorization": "Bearer " + os.environ["ESCALIO_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": os.environ["ESCALIO_REQUEST_ID"],
    },
)
try:
    with urlopen(request, timeout=30) as response:
        receipt = json.load(response)
        print(receipt["ticket"]["url"])
        # Persist this receipt with your saved batch before marking tasks processed.
except HTTPError as error:
    # Do not log request headers or the API key.
    print("Escalio returned HTTP", error.code)
    raise SystemExit(1)

JavaScript (Node.js)

import { readFile } from "node:fs/promises";

const response = await fetch(
  process.env.ESCALIO_ORIGIN.replace(/\/$/, "") + "/api/v1/tickets",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ESCALIO_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": process.env.ESCALIO_REQUEST_ID,
    },
    body: await readFile("ticket.json", "utf8"),
    signal: AbortSignal.timeout(30000),
  },
);
if (!response.ok) throw new Error(`Escalio returned HTTP ${response.status}`);
const receipt = await response.json();
console.log(receipt.ticket.url);
// Persist this receipt with your saved batch before marking tasks processed.

Replace the example links and batch ID with your own records. Your existing job remains responsible for remembering which source tasks were processed.

Request reference

Open the OpenAPI 3.1 specification

Headers
HeaderValue
AuthorizationBearer <organization API key>; the key starts with esc_org_.
Content-Typeapplication/json
Idempotency-KeyRequired. 8–100 letters, digits, dots, underscores, colons, slashes or hyphens.
JSON body
FieldContract
subjectRequired. 1–240 characters, not blank. No line breaks or control characters.
bodyRequired. 1–20,000 characters, not blank. Stored as an internal note; line breaks are allowed.
priorityOptional. low, normal (Medium in the UI), high or urgent. Default: normal.
requester_nameOptional. 1–120 characters, without line breaks or control characters.
requester_emailOptional. Valid email, up to 254 characters. Selects or creates a customer contact. Omit it for internal summaries across customers.
group_idOptional. UUID of a group in this organization. Does not assign an agent.
tagsOptional. Up to 20 strings. Tags are lowercased, leading # removed, whitespace replaced by underscores and duplicates removed. Each resulting tag is 1–50 characters without commas, #, quotes, angle brackets or control characters. Empty tags are ignored.

Unknown fields are rejected. The key and workspace host determine the organization; do not send an organization ID.

Choosing a requester

Without requester_email, Escalio uses a non-deliverable automation contact. Supplying a real email can make the ticket's subject and status visible to that customer in the portal; the internal note stays private. For a summary spanning several customers, omit requester details.

3. Keep the receipt

201 Created means a new ticket was created. An identical retry returns 200 OK with replayed: true. Save the returned ID and link with your job's batch record. The link opens the ticket for signed-in staff with access.

201 Created

{
  "ticket": {
    "id": "00000000-0000-4000-8000-000000000123",
    "url": "https://YOUR-WORKSPACE.escalio.org/app/tickets/00000000-0000-4000-8000-000000000123"
  },
  "replayed": false
}

Retry without duplicate tickets

  1. Persist a unique batch ID and the exact payload before the first request.
  2. After a timeout, connection failure or server error, retry with backoff using the same Idempotency-Key and payload. A timeout may occur after the ticket was saved.
  3. For 429, wait at least a minute, then retry the same operation. For 409, inspect the saved batch: the key already belongs to a different payload. Do not generate a new ID to bypass the conflict.
  4. Use a new ID only for a genuinely new batch. Save success before marking source tasks as processed.

Receipts are scoped to the organization, survive API-key rotation and currently remain for the organization's lifetime. Retrying a deleted ticket's operation returns its original receipt; it does not recreate the ticket. Different request IDs are different operations, even if their payloads match.

Errors & limits

Keys expire in 1–365 days (the UI offers 30, 90, 180 and 365). An organization can have up to 50 active keys. Each key can create 60 new tickets per minute; successful replays do not create tickets.

HTTP errors
StatusWhat to do
401Missing, invalid, expired or revoked key, or key for another organization. Check the workspace origin and replace the credential if needed.
402Workspace access is closed. An owner/admin must restore access in Billing.
403The credential lacks tickets:create permission. Use an organization key with that permission.
404Wrong host or unavailable workspace. Use the organization's workspace origin, not the main Escalio site or a custom portal domain.
409The Idempotency-Key already belongs to a different payload. Compare it with your saved batch; do not automatically create a new request ID.
422Invalid JSON fields, missing/invalid Idempotency-Key, or a group outside this organization. Check the field names, lengths and allowed values above.
428An owner/admin must accept the current Terms and DPA in the workspace. Retry the same batch after acceptance.
429Rate limit reached. Wait at least one minute, then retry with the same request ID and payload.
503Temporary service failure. Retry with backoff using the same request ID and payload. Keep the request_id if the problem persists.

Error responses contain code, detail and request_id. Keep the request ID for troubleshooting. Do not log the Authorization header or raw customer content.

401 — missing key

{
  "detail": "Send Authorization: Bearer <organization API key>.",
  "code": "organization_api_key_required",
  "request_id": "example-request-id"
}