{
 "openapi": "3.1.0",
 "info": {
  "title": "Shark Track API",
  "version": "1.0.0",
  "description": "Short links on shrk.in with per-click analytics.\n\nAuthenticate with a bearer key from Settings in the dashboard. Read keys can call any GET; write keys can also create, change and delete.\n\nTimestamps are epoch milliseconds. Tracking parameters never appear in the short link: they are applied on redirect and recorded against the click.",
  "contact": {
   "url": "https://docs.shrk.in"
  }
 },
 "servers": [
  {
   "url": "https://app.shrk.in/v1"
  }
 ],
 "security": [
  {
   "bearerAuth": []
  }
 ],
 "components": {
  "securitySchemes": {
   "bearerAuth": {
    "type": "http",
    "scheme": "bearer",
    "description": "Authorization: Bearer stk_live_\u2026"
   }
  },
  "schemas": {
   "Link": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "slug": {
      "type": "string"
     },
     "shortUrl": {
      "type": "string",
      "description": "The bare short link. Tracking parameters are applied on redirect, never shown here."
     },
     "destination": {
      "type": "string"
     },
     "title": {
      "type": [
       "string",
       "null"
      ]
     },
     "tags": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "status": {
      "type": "string",
      "enum": [
       "active",
       "paused",
       "archived"
      ]
     },
     "params": {
      "type": "object",
      "additionalProperties": {
       "type": "string"
      },
      "description": "Applied to the destination on every click and recorded per click."
     },
     "variants": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Variant"
      }
     },
     "projectId": {
      "type": [
       "string",
       "null"
      ]
     },
     "projectName": {
      "type": [
       "string",
       "null"
      ]
     },
     "clickCount": {
      "type": "integer",
      "description": "Human clicks. Bot hits are counted separately in botCount."
     },
     "uniqueCount": {
      "type": "integer"
     },
     "botCount": {
      "type": "integer"
     },
     "expiresAt": {
      "type": [
       "integer",
       "null"
      ]
     },
     "clickLimit": {
      "type": [
       "integer",
       "null"
      ]
     },
     "lastClickedAt": {
      "type": [
       "integer",
       "null"
      ]
     },
     "createdAt": {
      "type": "integer"
     },
     "updatedAt": {
      "type": "integer"
     }
    }
   },
   "Variant": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "code": {
      "type": "string"
     },
     "url": {
      "type": "string",
      "description": "shrk.in/<slug>/<code>"
     },
     "params": {
      "type": "object",
      "additionalProperties": {
       "type": "string"
      }
     },
     "clickCount": {
      "type": "integer"
     },
     "createdAt": {
      "type": "integer"
     },
     "updatedAt": {
      "type": "integer"
     }
    }
   },
   "Project": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "name": {
      "type": "string"
     },
     "linkCount": {
      "type": "integer"
     },
     "createdAt": {
      "type": "integer"
     },
     "updatedAt": {
      "type": "integer"
     }
    }
   },
   "Error": {
    "type": "object",
    "properties": {
     "error": {
      "type": "object",
      "required": [
       "code",
       "message"
      ],
      "properties": {
       "code": {
        "type": "string",
        "description": "Stable machine-readable code. Branch on this, never on the message."
       },
       "message": {
        "type": "string"
       },
       "field": {
        "type": [
         "string",
         "null"
        ]
       }
      }
     }
    }
   }
  }
 },
 "paths": {
  "/me": {
   "get": {
    "summary": "Who this key belongs to",
    "operationId": "getMe",
    "responses": {
     "200": {
      "description": "Key identity and scope",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     },
     "401": {
      "description": "Key missing, unknown, revoked or expired",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/links": {
   "get": {
    "summary": "List links",
    "operationId": "listLinks",
    "parameters": [
     {
      "name": "q",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Matches slug, title or destination."
     },
     {
      "name": "tag",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "project",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "A project id, or `none` for unfiled links."
     },
     {
      "name": "status",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "active",
        "paused",
        "archived"
       ]
      },
      "description": "Omit to get active and paused."
     },
     {
      "name": "sort",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "created",
        "oldest",
        "clicks",
        "updated"
       ]
      }
     },
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "integer",
       "maximum": 100
      }
     },
     {
      "name": "cursor",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "From the previous response. Null when there are no more."
     }
    ],
    "responses": {
     "200": {
      "description": "A page of links",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "items": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Link"
           }
          },
          "cursor": {
           "type": [
            "string",
            "null"
           ]
          }
         }
        }
       }
      }
     }
    }
   },
   "post": {
    "summary": "Create a link",
    "operationId": "createLink",
    "description": "Any utm_ keys on the destination are lifted into params automatically. Other query parameters stay on the destination. A custom slug requires a superuser key; leave it out for a generated one.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "destination"
        ],
        "properties": {
         "destination": {
          "type": "string"
         },
         "slug": {
          "type": "string"
         },
         "title": {
          "type": "string"
         },
         "tags": {
          "type": "array",
          "items": {
           "type": "string"
          }
         },
         "params": {
          "type": "object",
          "additionalProperties": {
           "type": "string"
          }
         },
         "projectId": {
          "type": [
           "string",
           "null"
          ]
         },
         "expiresAt": {
          "type": [
           "integer",
           "null"
          ]
         },
         "clickLimit": {
          "type": [
           "integer",
           "null"
          ]
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "The created link",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Link"
        }
       }
      }
     },
     "400": {
      "description": "Invalid destination or slug",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     },
     "403": {
      "description": "Custom slug on a non-superuser key, or read-only key",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     },
     "409": {
      "description": "Slug already taken",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/links/{id}": {
   "get": {
    "summary": "Get a link",
    "operationId": "getLink",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The link",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Link"
        }
       }
      }
     },
     "404": {
      "description": "No such link",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     }
    }
   },
   "patch": {
    "summary": "Update a link",
    "operationId": "updateLink",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "description": "Send only the fields you want to change. Setting status to archived hides a link while keeping it working.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "destination": {
          "type": "string"
         },
         "slug": {
          "type": "string"
         },
         "title": {
          "type": [
           "string",
           "null"
          ]
         },
         "tags": {
          "type": "array",
          "items": {
           "type": "string"
          }
         },
         "status": {
          "type": "string",
          "enum": [
           "active",
           "paused",
           "archived"
          ]
         },
         "params": {
          "type": "object",
          "additionalProperties": {
           "type": "string"
          }
         },
         "projectId": {
          "type": [
           "string",
           "null"
          ]
         },
         "expiresAt": {
          "type": [
           "integer",
           "null"
          ]
         },
         "clickLimit": {
          "type": [
           "integer",
           "null"
          ]
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The updated link",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Link"
        }
       }
      }
     },
     "403": {
      "description": "Renaming a slug needs a superuser key",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     }
    }
   },
   "delete": {
    "summary": "Delete a link",
    "operationId": "deleteLink",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "description": "Only links with no recorded human clicks can be deleted through the API. A link with clicks returns 409 with code `delete_requires_dashboard`; archive it instead, or delete it from the dashboard. Bot-only links are deletable.",
    "responses": {
     "200": {
      "description": "Deleted",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     },
     "409": {
      "description": "The link has recorded clicks",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/links/{id}/stats": {
   "get": {
    "summary": "Analytics for a link",
    "operationId": "getLinkStats",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "range",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "today",
        "24h",
        "7d",
        "30d",
        "90d",
        "all"
       ]
      },
      "description": "Defaults to 30d."
     },
     {
      "name": "from",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Epoch ms or YYYY-MM-DD. Overrides range."
     },
     {
      "name": "to",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "bots",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "0",
        "1"
       ]
      },
      "description": "1 includes bot traffic. Excluded by default."
     }
    ],
    "description": "Totals, a time series, and breakdowns by country, city, device, OS, browser, referrer, traffic type, UTM source, medium and campaign, custom parameters, variant and language. Also lists variant codes that were clicked but match no variant.",
    "responses": {
     "200": {
      "description": "Stats for the range",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/links/{id}/clicks": {
   "get": {
    "summary": "Recent clicks",
    "operationId": "listClicks",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "range",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "today",
        "24h",
        "7d",
        "30d",
        "90d",
        "all"
       ]
      },
      "description": "Defaults to 30d."
     },
     {
      "name": "from",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Epoch ms or YYYY-MM-DD. Overrides range."
     },
     {
      "name": "to",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "bots",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "0",
        "1"
       ]
      },
      "description": "1 includes bot traffic. Excluded by default."
     },
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "integer",
       "maximum": 200
      }
     },
     {
      "name": "cursor",
      "in": "query",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "A page of individual clicks",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/links/{id}/export.csv": {
   "get": {
    "summary": "Export clicks as CSV",
    "operationId": "exportClicks",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "range",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "today",
        "24h",
        "7d",
        "30d",
        "90d",
        "all"
       ]
      },
      "description": "Defaults to 30d."
     },
     {
      "name": "from",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Epoch ms or YYYY-MM-DD. Overrides range."
     },
     {
      "name": "to",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "bots",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "0",
        "1"
       ]
      },
      "description": "1 includes bot traffic. Excluded by default."
     }
    ],
    "responses": {
     "200": {
      "description": "CSV of raw clicks",
      "content": {
       "text/csv": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  },
  "/links/{id}/qr.svg": {
   "get": {
    "summary": "QR code",
    "operationId": "getQr",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "size",
      "in": "query",
      "schema": {
       "type": "integer",
       "minimum": 96,
       "maximum": 2048
      }
     },
     {
      "name": "variant",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Encode a variant URL instead of the base link."
     }
    ],
    "responses": {
     "200": {
      "description": "An SVG QR code",
      "content": {
       "image/svg+xml": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  },
  "/links/{id}/variants": {
   "get": {
    "summary": "List variants",
    "operationId": "listVariants",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Variants on this link",
      "content": {
       "application/json": {
        "schema": {
         "type": "array",
         "items": {
          "$ref": "#/components/schemas/Variant"
         }
        }
       }
      }
     }
    }
   },
   "post": {
    "summary": "Create a variant",
    "operationId": "createVariant",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "description": "A variant is a second address to the same destination: shrk.in/<slug>/<code>. Its params layer over the link's.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "code"
        ],
        "properties": {
         "code": {
          "type": "string",
          "description": "1-32 letters, numbers, hyphens or underscores."
         },
         "params": {
          "type": "object",
          "additionalProperties": {
           "type": "string"
          }
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "The created variant",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Variant"
        }
       }
      }
     },
     "409": {
      "description": "Code already used on this link",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/links/{id}/variants/{variantId}": {
   "patch": {
    "summary": "Update a variant",
    "operationId": "updateVariant",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "variantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "code": {
          "type": "string"
         },
         "params": {
          "type": "object",
          "additionalProperties": {
           "type": "string"
          }
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The updated variant",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Variant"
        }
       }
      }
     }
    }
   },
   "delete": {
    "summary": "Delete a variant",
    "operationId": "deleteVariant",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "variantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "description": "Past clicks are kept and report as an unrecognised code afterwards.",
    "responses": {
     "200": {
      "description": "Deleted",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/links/bulk/create": {
   "post": {
    "summary": "Create many links",
    "operationId": "bulkCreate",
    "description": "One URL per line, up to 200. A tab after a URL makes the rest its title. utm_ keys are lifted from each line. Exact repeats are skipped.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "text"
        ],
        "properties": {
         "text": {
          "type": "string"
         },
         "tags": {
          "type": "array",
          "items": {
           "type": "string"
          }
         },
         "projectId": {
          "type": [
           "string",
           "null"
          ]
         },
         "params": {
          "type": "object",
          "additionalProperties": {
           "type": "string"
          },
          "description": "Applied to every line. A line's own utm_ values win."
         },
         "skipDuplicates": {
          "type": "boolean",
          "default": true
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Created links and skipped lines",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/links/bulk/preview": {
   "post": {
    "summary": "Preview a bulk paste",
    "operationId": "bulkPreview",
    "description": "Parses without creating. Use this to check what a paste will do.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "text"
        ],
        "properties": {
         "text": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Per-line parse result",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/links/bulk/params": {
   "post": {
    "summary": "Apply parameters to many links",
    "operationId": "bulkParams",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "ids",
         "params"
        ],
        "properties": {
         "ids": {
          "type": "array",
          "items": {
           "type": "string"
          },
          "maxItems": 200
         },
         "params": {
          "type": "object",
          "additionalProperties": {
           "type": "string"
          }
         },
         "mode": {
          "type": "string",
          "enum": [
           "merge",
           "replace"
          ],
          "default": "merge"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "How many were updated",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/links/bulk/project": {
   "post": {
    "summary": "Move many links into a project",
    "operationId": "bulkProject",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "ids"
        ],
        "properties": {
         "ids": {
          "type": "array",
          "items": {
           "type": "string"
          },
          "maxItems": 200
         },
         "projectId": {
          "type": [
           "string",
           "null"
          ],
          "description": "Null unfiles them."
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "How many were moved",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/links/tags": {
   "get": {
    "summary": "List tags in use",
    "operationId": "listTags",
    "responses": {
     "200": {
      "description": "Tags with counts",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/projects": {
   "get": {
    "summary": "List projects",
    "operationId": "listProjects",
    "responses": {
     "200": {
      "description": "Projects with counts",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "projects": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Project"
           }
          },
          "unfiledCount": {
           "type": "integer"
          },
          "totalCount": {
           "type": "integer"
          }
         }
        }
       }
      }
     }
    }
   },
   "post": {
    "summary": "Create a project",
    "operationId": "createProject",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "name"
        ],
        "properties": {
         "name": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "The created project",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Project"
        }
       }
      }
     },
     "409": {
      "description": "Name already used",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "object",
           "required": [
            "code",
            "message"
           ],
           "properties": {
            "code": {
             "type": "string",
             "description": "Stable machine-readable code. Branch on this, never on the message."
            },
            "message": {
             "type": "string"
            },
            "field": {
             "type": [
              "string",
              "null"
             ]
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/projects/{id}": {
   "patch": {
    "summary": "Rename a project",
    "operationId": "updateProject",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "name"
        ],
        "properties": {
         "name": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Renamed",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   },
   "delete": {
    "summary": "Delete a project",
    "operationId": "deleteProject",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "description": "Links inside are unfiled, never deleted.",
    "responses": {
     "200": {
      "description": "Deleted",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/workspace/stats": {
   "get": {
    "summary": "Analytics across every link",
    "operationId": "getWorkspaceStats",
    "parameters": [
     {
      "name": "range",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "today",
        "24h",
        "7d",
        "30d",
        "90d",
        "all"
       ]
      },
      "description": "Defaults to 30d."
     },
     {
      "name": "from",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Epoch ms or YYYY-MM-DD. Overrides range."
     },
     {
      "name": "to",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "bots",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "0",
        "1"
       ]
      },
      "description": "1 includes bot traffic. Excluded by default."
     }
    ],
    "responses": {
     "200": {
      "description": "Totals, series and top breakdowns",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/workspace": {
   "get": {
    "summary": "Workspace details",
    "operationId": "getWorkspace",
    "responses": {
     "200": {
      "description": "Name, hostname and app host",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  }
 }
}