{
  "openapi": "3.1.0",
  "info": {
    "title": "Escalio Organization API",
    "version": "1.0.0",
    "description": "Organization-owned server-to-server ticket creation. Other Escalio API families use separate credentials."
  },
  "servers": [
    {
      "url": "https://{workspace}.escalio.org",
      "variables": {
        "workspace": {
          "default": "YOUR-WORKSPACE",
          "description": "Your organization's workspace subdomain"
        }
      }
    }
  ],
  "components": {
    "securitySchemes": {
      "organizationKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Organization API key (esc_org_ prefix), tickets:create permission. Get it from your avatar > API keys or Settings > Team > API keys."
      }
    },
    "schemas": {
      "NewApiTicket": {
        "additionalProperties": false,
        "properties": {
          "subject": {
            "maxLength": 240,
            "minLength": 1,
            "title": "Subject",
            "type": "string",
            "description": "Required. 1\u2013240 characters, not blank. No line breaks or control characters."
          },
          "body": {
            "maxLength": 20000,
            "minLength": 1,
            "title": "Body",
            "type": "string",
            "description": "Required. 1\u201320,000 characters, not blank. Stored as an internal note; line breaks are allowed."
          },
          "priority": {
            "default": "normal",
            "enum": [
              "low",
              "normal",
              "high",
              "urgent"
            ],
            "title": "Priority",
            "type": "string",
            "description": "Optional. low, normal (Medium in the UI), high or urgent. Default: normal."
          },
          "requester_name": {
            "anyOf": [
              {
                "maxLength": 120,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Requester Name",
            "description": "Optional. 1\u2013120 characters, without line breaks or control characters."
          },
          "requester_email": {
            "anyOf": [
              {
                "format": "email",
                "maxLength": 254,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Requester Email",
            "description": "Optional. Valid email, up to 254 characters. Selects or creates a customer contact. Omit it for internal summaries across customers."
          },
          "group_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Group Id",
            "description": "Optional. UUID of a group in this organization. Does not assign an agent."
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "maxItems": 20,
            "title": "Tags",
            "type": "array",
            "description": "Optional. Up to 20 strings. Tags are lowercased, leading # removed, whitespace replaced by underscores and duplicates removed. Each resulting tag is 1\u201350 characters without commas, #, quotes, angle brackets or control characters. Empty tags are ignored."
          }
        },
        "required": [
          "subject",
          "body"
        ],
        "title": "NewApiTicket",
        "type": "object"
      }
    }
  },
  "paths": {
    "/api/v1/tickets": {
      "post": {
        "operationId": "createOrganizationTicket",
        "summary": "Create an open, unassigned ticket with an internal note",
        "description": "Requires active organization access and accepted Terms/DPA. Existing ticket-created automations may run; the API does not itself send a customer message. Use a stable request ID across retries and key rotation.",
        "security": [
          {
            "organizationKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 100,
              "pattern": "^[A-Za-z0-9._:/-]{8,100}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewApiTicket"
              },
              "example": {
                "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"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "New ticket created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ticket",
                    "replayed"
                  ],
                  "properties": {
                    "ticket": {
                      "type": "object",
                      "required": [
                        "id",
                        "url"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        }
                      }
                    },
                    "replayed": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "ticket": {
                    "id": "00000000-0000-4000-8000-000000000123",
                    "url": "https://YOUR-WORKSPACE.escalio.org/app/tickets/00000000-0000-4000-8000-000000000123"
                  },
                  "replayed": false
                }
              }
            }
          },
          "200": {
            "description": "Identical request replayed; no new ticket",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ticket",
                    "replayed"
                  ],
                  "properties": {
                    "ticket": {
                      "type": "object",
                      "required": [
                        "id",
                        "url"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        }
                      }
                    },
                    "replayed": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "ticket": {
                    "id": "00000000-0000-4000-8000-000000000123",
                    "url": "https://YOUR-WORKSPACE.escalio.org/app/tickets/00000000-0000-4000-8000-000000000123"
                  },
                  "replayed": true
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key, or key for another organization. Check the workspace origin and replace the credential if needed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "detail": "Send Authorization: Bearer <organization API key>.",
                  "code": "organization_api_key_required",
                  "request_id": "example-request-id"
                }
              }
            }
          },
          "402": {
            "description": "Workspace access is closed. An owner/admin must restore access in Billing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Wrong host or unavailable workspace. Use the organization's workspace origin, not the main Escalio site or a custom portal domain.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key already belongs to a different payload. Compare it with your saved batch; do not automatically create a new request ID.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid JSON fields, missing/invalid Idempotency-Key, or a group outside this organization. Check the field names, lengths and allowed values above.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "428": {
            "description": "An owner/admin must accept the current Terms and DPA in the workspace. Retry the same batch after acceptance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. Wait at least one minute, then retry with the same request ID and payload.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Temporary service failure. Retry with backoff using the same request ID and payload. Keep the request_id if the problem persists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The credential lacks tickets:create permission. Use an organization key with that permission.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "detail",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "ticket.json",
            "lang": "JSON",
            "source": "{\n  \"subject\": \"Released fixes to follow up\",\n  \"body\": \"Released fix: https://issues.example.com/APP-123\\nCustomer follow-up: https://tasks.example.com/task-456\",\n  \"priority\": \"normal\",\n  \"tags\": [\n    \"release_followup\"\n  ]\n}"
          },
          {
            "label": "Environment (POSIX shell)",
            "lang": "Shell",
            "source": "export ESCALIO_ORIGIN='https://YOUR-WORKSPACE.escalio.org'\nexport ESCALIO_REQUEST_ID='release-followup-batch-001'\n# Supply ESCALIO_API_KEY from your job's secret store; do not commit it."
          },
          {
            "label": "cURL (POSIX shell)",
            "lang": "Shell",
            "source": "curl --fail-with-body --show-error --max-time 30 \\\n  --request POST \"$ESCALIO_ORIGIN/api/v1/tickets\" \\\n  --header \"Authorization: Bearer $ESCALIO_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header \"Idempotency-Key: $ESCALIO_REQUEST_ID\" \\\n  --data-binary @ticket.json"
          },
          {
            "label": "Python 3",
            "lang": "Python",
            "source": "import json\nimport os\nfrom pathlib import Path\nfrom urllib.error import HTTPError\nfrom urllib.request import Request, urlopen\n\nrequest = Request(\n    os.environ[\"ESCALIO_ORIGIN\"].rstrip(\"/\") + \"/api/v1/tickets\",\n    data=Path(\"ticket.json\").read_bytes(),\n    method=\"POST\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"ESCALIO_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": os.environ[\"ESCALIO_REQUEST_ID\"],\n    },\n)\ntry:\n    with urlopen(request, timeout=30) as response:\n        receipt = json.load(response)\n        print(receipt[\"ticket\"][\"url\"])\n        # Persist this receipt with your saved batch before marking tasks processed.\nexcept HTTPError as error:\n    # Do not log request headers or the API key.\n    print(\"Escalio returned HTTP\", error.code)\n    raise SystemExit(1)\n"
          },
          {
            "label": "JavaScript (Node.js)",
            "lang": "JavaScript",
            "source": "import { readFile } from \"node:fs/promises\";\n\nconst response = await fetch(\n  process.env.ESCALIO_ORIGIN.replace(/\\/$/, \"\") + \"/api/v1/tickets\",\n  {\n    method: \"POST\",\n    headers: {\n      Authorization: `Bearer ${process.env.ESCALIO_API_KEY}`,\n      \"Content-Type\": \"application/json\",\n      \"Idempotency-Key\": process.env.ESCALIO_REQUEST_ID,\n    },\n    body: await readFile(\"ticket.json\", \"utf8\"),\n    signal: AbortSignal.timeout(30000),\n  },\n);\nif (!response.ok) throw new Error(`Escalio returned HTTP ${response.status}`);\nconst receipt = await response.json();\nconsole.log(receipt.ticket.url);\n// Persist this receipt with your saved batch before marking tasks processed.\n"
          }
        ]
      }
    }
  }
}
