{
  "openapi": "3.1.0",
  "info": {
    "title": "AseWeb Decoder API",
    "version": "1.3.0",
    "description": "ionCube and SourceGuardian decoding. Save a webhook URL once in Developer \u2192 Webhook and receive JSON completion notifications automatically. No webhook secret or signature setup. Decode deliveries retry up to eight times total. JSON timestamps use Unix milliseconds.",
    "contact": {
      "url": "https://t.me/amirmalek0"
    }
  },
  "servers": [
    {
      "url": "https://aseweb.ir/api/v1"
    }
  ],
  "security": [
    {
      "BearerKey": []
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "operationId": "getAccount",
        "summary": "Check access and current upload limits",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        }
                      }
                    },
                    "access": {
                      "enum": [
                        "demo",
                        "full"
                      ]
                    },
                    "subscriptionExpiresAt": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "previewLimits": {
                      "type": "object",
                      "properties": {
                        "lines": {
                          "type": "integer"
                        },
                        "characters": {
                          "type": "integer"
                        },
                        "zip": {
                          "type": "string"
                        }
                      }
                    },
                    "uploadLimits": {
                      "type": "object",
                      "properties": {
                        "phpBytes": {
                          "type": "integer"
                        },
                        "zipBytes": {
                          "type": "integer"
                        },
                        "phpFilesPerZip": {
                          "type": "integer"
                        }
                      }
                    },
                    "retentionDays": {
                      "type": "integer"
                    },
                    "webhooks": {
                      "type": "object",
                      "properties": {
                        "configured": {
                          "type": "boolean"
                        },
                        "enabled": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "configured",
                        "enabled"
                      ],
                      "description": "Saved account setting; configure the URL in Developer \u2192 Webhook."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied: IP banned (code ip_banned), or subscription changed during upload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/jobs": {
      "post": {
        "operationId": "createDecode",
        "summary": "Submit an encoded PHP file or ZIP",
        "description": "The key identifies the account. Full results require an active subscription; otherwise only a demo is retained. No file credits are spent. Do not automatically retry this non-idempotent POST. If enabled in Developer \u2192 Webhook, the saved account webhook is used automatically for this upload. Do not include webhook_url or webhook_secret; per-request callback fields return HTTP 400. Queued completions also notify. HTTP-rejected uploads do not notify. Delivery can begin before the upload response reaches you.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "decoder": {
                    "type": "string",
                    "enum": [
                      "ioncube",
                      "sourceguardian"
                    ]
                  },
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "decoder",
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted; poll job status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job": {
                      "$ref": "#/components/schemas/Job"
                    }
                  }
                }
              }
            },
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied: IP banned (code ip_banned), or subscription changed during upload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listDecodes",
        "summary": "List this account\u2019s API submissions, newest first",
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "Use the previous response\u2019s nextCursor; 50 records per page."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied: IP banned (code ip_banned), or subscription changed during upload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobID"
        }
      ],
      "get": {
        "operationId": "getDecode",
        "summary": "Refresh status, errors and queue estimate",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job": {
                      "$ref": "#/components/schemas/Job"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied: IP banned (code ip_banned), or subscription changed during upload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}/preview": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobID"
        }
      ],
      "get": {
        "operationId": "previewDecode",
        "summary": "Read decoded PHP as JSON",
        "description": "ZIP: the first successfully decoded PHP file. Demo: at most 15 formatted lines and 1500 Unicode characters.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Preview"
                }
              }
            }
          },
          "400": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied: IP banned (code ip_banned), or subscription changed during upload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobID"
        }
      ],
      "get": {
        "operationId": "downloadDecode",
        "summary": "Download the result appropriate to this key\u2019s current access",
        "description": "Full: complete PHP or ZIP. Demo: plain-text PHP prefix only, never a ZIP. Expired subscriptions receive demo output, including for previously full API jobs.",
        "responses": {
          "200": {
            "description": "Attachment; filename from Content-Disposition",
            "headers": {
              "Content-Disposition": {
                "schema": {
                  "type": "string"
                }
              },
              "X-AseWeb-Access": {
                "schema": {
                  "enum": [
                    "demo",
                    "full"
                  ]
                },
                "description": "Access applied to this attachment"
              }
            },
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/x-httpd-php": {
                "schema": {
                  "type": "string"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied: IP banned (code ip_banned), or subscription changed during upload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Request rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}/webhook": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobID"
        }
      ],
      "get": {
        "operationId": "getWebhookDelivery",
        "summary": "Inspect webhook delivery state and attempts",
        "description": "Owned jobs only. Does not expose callback URLs, secrets, receiver response bodies or internal decoder details. Eight total attempts; poll/download remain available if delivery exhausts.",
        "responses": {
          "200": {
            "description": "Delivery state and ordered attempt history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook": {
                      "$ref": "#/components/schemas/WebhookState"
                    },
                    "attempts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "attempt": {
                            "type": "integer"
                          },
                          "startedAt": {
                            "type": "integer"
                          },
                          "finishedAt": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "httpStatus": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IP banned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Job not owned, not found, or no webhook configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Create a key under Developer \u2192 Your API keys. Send Authorization: Bearer ase_\u2026; never put keys in URLs or frontend code."
      }
    },
    "parameters": {
      "JobID": {
        "in": "path",
        "name": "id",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-f0-9]{32}$"
        }
      }
    },
    "schemas": {
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "decoder": {
            "enum": [
              "ioncube",
              "sourceguardian"
            ]
          },
          "originalName": {
            "type": "string"
          },
          "status": {
            "enum": [
              "submitting",
              "queued",
              "decoding",
              "complete",
              "partial",
              "failed"
            ]
          },
          "access": {
            "enum": [
              "demo",
              "full"
            ]
          },
          "submittedAccess": {
            "enum": [
              "demo",
              "full"
            ]
          },
          "createdAt": {
            "type": "integer"
          },
          "expiresAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "fileCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "successCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "failedCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "result": {
                  "type": "string"
                },
                "error": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "estimatedWait": {
            "type": [
              "string",
              "null"
            ]
          },
          "queuePosition": {
            "type": [
              "integer",
              "null"
            ]
          },
          "downloadable": {
            "type": "boolean"
          },
          "links": {
            "type": "object",
            "properties": {
              "self": {
                "type": "string"
              },
              "preview": {
                "type": "string"
              },
              "download": {
                "type": "string"
              }
            }
          },
          "webhook": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WebhookState"
              },
              {
                "type": "null"
              }
            ],
            "description": "Included in upload and individual job-status responses. Not included in list responses. Null when no webhook was configured."
          }
        },
        "required": [
          "id",
          "status",
          "access",
          "submittedAccess",
          "links"
        ]
      },
      "Preview": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "description": "Full first PHP file for active subscribers; bounded demo otherwise."
          },
          "access": {
            "enum": [
              "demo",
              "full"
            ]
          },
          "truncated": {
            "type": "boolean",
            "description": "True for demo access, even if the file is shorter than the preview limit."
          },
          "outputName": {
            "type": "string"
          },
          "totalLines": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Formatted line count, present for demo results."
          },
          "expiresAt": {
            "type": "integer"
          }
        },
        "required": [
          "source",
          "access",
          "truncated",
          "outputName",
          "expiresAt"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "ip_banned"
            ],
            "description": "Present when website and API access are denied by an IP ban."
          }
        },
        "required": [
          "error"
        ]
      },
      "WebhookState": {
        "type": "object",
        "properties": {
          "eventId": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "status": {
            "enum": [
              "waiting",
              "pending",
              "delivered",
              "exhausted",
              "cancelled"
            ]
          },
          "attempts": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8
          },
          "maxAttempts": {
            "const": 8
          },
          "nextAttemptAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "lastHttpStatus": {
            "type": [
              "integer",
              "null"
            ]
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          },
          "deliveredAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "type": "integer"
          },
          "updatedAt": {
            "type": "integer"
          }
        },
        "required": [
          "eventId",
          "status",
          "attempts",
          "maxAttempts"
        ]
      },
      "WebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "type": {
            "enum": [
              "decode.complete",
              "decode.partial",
              "decode.failed"
            ]
          },
          "createdAt": {
            "type": "integer",
            "description": "Unix milliseconds; stable notification creation time."
          },
          "data": {
            "type": "object",
            "required": [
              "id",
              "status",
              "decoder",
              "submittedAccess",
              "links"
            ],
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-f0-9]{32}$"
              },
              "status": {
                "enum": [
                  "complete",
                  "partial",
                  "failed"
                ]
              },
              "decoder": {
                "enum": [
                  "ioncube",
                  "sourceguardian"
                ]
              },
              "originalName": {
                "type": "string"
              },
              "submittedAccess": {
                "enum": [
                  "demo",
                  "full"
                ],
                "description": "Original submission access. Downloads still require Bearer auth and current subscription eligibility."
              },
              "expiresAt": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "fileCount": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "successCount": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "failedCount": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "links": {
                "type": "object",
                "properties": {
                  "self": {
                    "type": "string",
                    "format": "uri"
                  },
                  "preview": {
                    "type": "string",
                    "format": "uri"
                  },
                  "download": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "example": {
          "id": "abcdef0123456789abcdef0123456789",
          "type": "decode.complete",
          "createdAt": 1794139200000,
          "data": {
            "id": "0123456789abcdef0123456789abcdef",
            "status": "complete",
            "decoder": "ioncube",
            "originalName": "encoded.php",
            "submittedAccess": "full",
            "expiresAt": 1794744000000,
            "fileCount": 1,
            "successCount": 1,
            "failedCount": 0,
            "error": null,
            "links": {
              "self": "https://aseweb.ir/api/v1/jobs/0123456789abcdef0123456789abcdef",
              "preview": "https://aseweb.ir/api/v1/jobs/0123456789abcdef0123456789abcdef/preview",
              "download": "https://aseweb.ir/api/v1/jobs/0123456789abcdef0123456789abcdef/download"
            }
          }
        }
      },
      "WebhookTestEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "type": {
            "const": "webhook.test"
          },
          "createdAt": {
            "type": "integer"
          },
          "data": {
            "type": "object",
            "required": [
              "id",
              "test",
              "message"
            ],
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-f0-9]{32}$",
                "description": "Test ID, not a decode job ID."
              },
              "test": {
                "const": true
              },
              "message": {
                "type": "string",
                "example": "This is an AseWeb webhook test. No file was decoded and no credits were used."
              }
            }
          }
        },
        "description": "JSON manual test. No file, credit usage, decode job or automatic retry."
      }
    }
  },
  "webhooks": {
    "decodeResult": {
      "post": {
        "operationId": "receiveDecodeEvent",
        "summary": "Receive a JSON decode notification or manual test",
        "security": [],
        "description": "POST to the URL saved in Developer \u2192 Webhook. No webhook secret or signature is required. Return HTTP 2xx within 10 seconds. Decode notifications retry up to eight total attempts with delays 1m,5m,15m,1h,3h,6h,12h. The event ID and JSON body stay the same on retries. Manual webhook.test events make one attempt and use no credits. Confirm authoritative decode status and download results through the authenticated API.",
        "parameters": [
          {
            "in": "header",
            "name": "X-AseWeb-Event-ID",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Informational event ID, also in the JSON body. Deduplicate by this ID."
          },
          {
            "in": "header",
            "name": "X-AseWeb-Timestamp",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Informational Unix seconds at delivery."
          },
          {
            "in": "header",
            "name": "X-AseWeb-Attempt",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Delivery attempt number. Up to eight for decode notifications; one for manual tests."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "$ref": "#/components/schemas/WebhookTestEvent"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Event durably accepted; duplicate events must also return success."
          },
          "default": {
            "description": "Delivery failed; retry while attempts remain."
          }
        }
      }
    }
  }
}
