# qURL™ Service API

- **OpenAPI Version:** `3.1.1`
- **API Version:** `1.0.0`

API for managing qURL resources — secure, time-limited access links to protected resources.

**Quantum URL (qURL)** · The internet has a hidden layer. This is how you enter.

## Authentication

The public API requires Auth0 JWT tokens or API keys with appropriate scopes:

- `qurl:read` - Read access to qURLs and quota
- `qurl:write` - Create, update, and delete qURLs
- `qurl:resolve` - Resolve access tokens and trigger firewall access (headless)
- `qurl:agent` - Bootstrap LayerV reverse-tunnel agents (POST /v1/agent/bootstrap)
- `qurl:write` also covers webhook management

## Rate Limiting

Per-owner limits are configurable and vary by plan. Rate-limit exceeded responses include a `Retry-After` header.

## Idempotency

Mutating endpoints (POST, PUT, PATCH) support idempotency via the `Idempotency-Key` header. When provided, the response is cached and subsequent requests with the same key return the cached response with `Idempotency-Replayed: true` header.

## Response Envelope

All responses use a consistent envelope format:

```json
{
  "data": { ... },      // For success responses
  "error": { ... },     // For error responses (RFC 7807 format)
  "meta": {
    "request_id": "...",
    "page_size": 20,    // For list responses
    "has_more": true,   // For list responses
    "next_cursor": "..."// For list responses
  }
}
```

## Error Responses (RFC 7807)

Error responses follow [RFC 7807 Problem Details](https://datatracker.ietf.org/doc/html/rfc7807) with `Content-Type: application/problem+json`:

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": { "request_id": "..." }
}
```

For validation errors, an additional `invalid_fields` map is included.

## Caching

GET endpoints support ETag-based caching. Include `If-None-Match` header with the ETag value to receive a 304 Not Modified response when content hasn't changed.

## Object Model

- **Resource** (`r_` prefix): Long-lived container grouped by target URL. Holds metadata (tags, description, custom domain). Auto-created on first qURL for a target URL.
- **qURL / Access Token** (`q_` display ID, `at_` token): Short-lived, policy-bound access token. Belongs to one Resource. Owns expiry, access policy, session limits.
- **Access Link**: The `qurl.link/at_xxx` URL returned once at creation time.

The `/v1/qurls` endpoints are resource-centric — `{id}` accepts both resource IDs (`r_`) and qURL display IDs (`q_`). Per-qURL management uses `DELETE /v1/resources/{id}/qurls/{qurl_id}` to revoke individual tokens.

## Webhooks

Subscribe to events via webhook endpoints. Webhook payloads are signed with HMAC-SHA256 using your webhook secret. Verify the `QURL-Signature` header.

## Servers

- **URL:** `https://api.layerv.ai`
  - **Description:** Production
- **URL:** `https://api.layerv.xyz`
  - **Description:** Sandbox

## Operations

### Resolve a qURL access token (headless)

- **Method:** `POST`
- **Path:** `/v1/resolve`
- **Tags:** Resolution

Resolves a qURL access token and triggers an NHP knock to open firewall access for the caller's IP address. Returns the target URL that the caller can access directly for the duration of the access grant.

This endpoint is designed for AI agents and programmatic clients that cannot follow the browser-based qURL flow.

**Important:** The firewall is opened for the IP address making this request. The `src_ip` in the response reflects which IP was granted access.

**One-time tokens:** If the token is one-time-use and the NHP knock fails after token resolution, the token is consumed but no access is granted. Multi-use tokens can be retried.

#### Request Body

##### Content-Type: application/json

- **`access_token` (required)**

  `string` — qURL access token (e.g., "at\_k8xqp9h2sj9lx7r4abcdef")

**Example:**

```json
{
  "access_token": "at_k8xqp9h2sj9lx7r4abcdef"
}
```

#### Responses

##### Status: 200 Token resolved and firewall access granted

###### Content-Type: application/json

- **`data`**

  `object`

  - **`access_grant`**

    `object` — Details of the firewall access that was granted

    - **`expires_in`**

      `integer` — Seconds until firewall access expires

    - **`granted_at`**

      `string`, format: `date-time` — When the access was granted

    - **`src_ip`**

      `string` — The IP address that was granted access

  - **`resource_id`**

    `string` — qURL resource identifier

  - **`target_url`**

    `string`, format: `uri` — The URL that is now accessible from the caller's IP

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "target_url": "https://api.example.com/data",
    "resource_id": "r_k8xqp9h2sj9",
    "access_grant": {
      "expires_in": 305,
      "granted_at": "2026-03-09T15:30:00Z",
      "src_ip": "203.0.113.42"
    }
  },
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 410 Resource is no longer available (consumed, expired, or revoked)

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 502 Upstream NHP knock failed

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Bootstrap a LayerV reverse-tunnel agent

- **Method:** `POST`
- **Path:** `/v1/agent/bootstrap`
- **Tags:** Agent

Registers a LayerV agent's X25519 public key and returns the NHP peer info the agent needs to perform its first knock.

Re-bootstrapping with a new `public_key` for the same `agent_id` is idempotent (key rotation).

**Authentication:** API key with the `qurl:agent` scope. JWTs are rejected with 403.

**Rate limit:** 10 per hour per API key.

**Audit:** every outcome is recorded.

#### Request Body

##### Content-Type: application/json

- **`public_key` (required)**

  `string` — Agent's X25519 public key, raw 32 bytes encoded as standard base64 (RFC 4648 §4 — \`+\`/\`/\` alphabet, \`=\` padding).

- **`agent_id`**

  `string` — Optional label correlating this agent across requests (matches \`LAYERV\_AGENT\_ID\` on the agent). The server generates one when omitted.

- **`hostname`**

  `string` — Optional hostname where the agent is running (audit log only). If a previous bootstrap recorded a value, omitting this field (or sending an empty string) preserves the prior value; send a non-empty string to replace it. There is no wire shape to clear a previously-set hostname back to empty — audit lineage is intentionally append-only.

- **`version`**

  `string` — Optional agent build version (audit log only). Same merge behavior as \`hostname\`: omitting or sending empty preserves the prior value; send a non-empty string to replace it. Cannot be cleared back to empty (same audit-lineage rule).

**Example:**

```json
{
  "public_key": "62cFrVBeF1Tl7lUAJ9MNa9lFykVf6D7mNqLaEYggFN0=",
  "agent_id": "prod-us-east-1",
  "hostname": "agent-7f8c9b-prod-use1",
  "version": "1.4.2"
}
```

#### Responses

##### Status: 200 Agent registered.

###### Content-Type: application/json

- **`data` (required)**

  `object` — Bootstrap response payload. An agent's registration may expire after roughly 90 days of inactivity; long-lived agents should re-bootstrap on restart. Re-bootstrapping with the same \`agent\_id\` is safe and returns the original \`registered\_at\`.

  - **`agent_id` (required)**

    `string` — Label for the agent; echoes the request value, or the server-generated identifier when none was supplied.

  - **`nhp_server_peer` (required)**

    `object` — Peer info the agent needs to perform its first NHP knock.

    - **`expire_time` (required)**

      `integer`, format: `int64` — Unix timestamp (seconds) at which this peer entry should be considered stale, or 0 for "no client-side expiry".

    - **`host` (required)**

      `string` — NHP server hostname or IP (no scheme).

    - **`port` (required)**

      `integer` — NHP server UDP port.

    - **`public_key_b64` (required)**

      `string` — NHP server X25519 public key, base64-encoded (32 raw bytes).

  - **`registered_at` (required)**

    `string`, format: `date-time` — RFC3339 timestamp of the first registration for this \`agent\_id\`. Re-bootstraps return the original value, not the current time.

- **`meta` (required)**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "agent_id": "",
    "registered_at": "",
    "nhp_server_peer": {
      "public_key_b64": "",
      "host": "nhp.layerv.ai",
      "port": 62206,
      "expire_time": 0
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 409 Concurrent registration conflict for the same \`agent\_id\`. \*\*Retry\*\*: re-issue the same request body with exponential backoff (e.g., 1s / 4s / 16s, capped at 60s). Registration is idempotent on \`agent\_id\`, so resending the same \`public\_key\` is safe. Do NOT regenerate the keypair on 409.

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Create a new qURL

- **Method:** `POST`
- **Path:** `/v1/qurls`
- **Tags:** QURLs

Creates a qURL access token for a target URL. Auto-creates a resource if none exists for the target URL.

#### Request Body

##### Content-Type: application/json

- **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

- **`custom_domain`**

  `string` — Optional custom domain to assign to the auto-created resource. The domain must be registered, active, and owned by the caller. Only allowed when the target URL creates a new resource — rejected if a resource already exists for this target URL.

- **`expires_in`**

  `string` — Duration until expiration. Supports: hours (24h), days (7d), weeks (1w). Minimum: 1 minute. Maximum varies by plan: free=3 days, growth/enterprise=30 days. Default: 24h.

- **`label`**

  `string` — Human-readable label identifying who this qURL is for

- **`max_sessions`**

  `integer` — Maximum concurrent sessions allowed. 0 = unlimited (default), 1-1000 = hard limit.

- **`one_time_use`**

  `boolean`, default: `false` — Whether this qURL expires after a single use

- **`session_duration`**

  `string` — How long access lasts after someone clicks this qURL. Controls the session/cookie lifetime, separate from the qURL link expiration (expires\_in). Supports: seconds (30s), minutes (5m), hours (1h), days (1d). Minimum: 1 second. Maximum: 24 hours. Default: server default (typically 1 hour).

- **`target_url`**

  `string`, format: `uri` — The URL to protect with qURL (max 2048 characters). Required for standard reverse-proxy qURLs.

- **`type`**

  `string`, default: `"url"` — Resource type. The handler accepts only \`url\` on this public surface; see the ResourceType schema for the full runtime enum.

**Example:**

```json
{
  "type": "url",
  "target_url": "https://internal.example.com/dashboard",
  "expires_in": "7d",
  "one_time_use": false,
  "max_sessions": 0,
  "session_duration": "1h",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  },
  "label": "Alice from Acme",
  "custom_domain": "app.example.com"
}
```

#### Responses

##### Status: 201 qURL created successfully

###### Content-Type: application/json

- **`data`**

  `object` — Response data for qURL creation

  - **`branded_domain`**

    `string` — Customer's bare branded hostname for anchor-text display alongside \`qurl\_link\`. See \`MintLinkData.branded\_domain\` for the canonical description (rendering pattern, \`provisioning\_tls\` caveat, live-status gating). Populated when the resource has a \`custom\_domain\` whose status is usable; omitted otherwise. Note: requests replayed via \`Idempotency-Key\` return the cached response, so \`branded\_domain\` reflects the domain status at the time of the original request — not the current status. Consumers caching create responses should re-fetch via the resource if they need a live usability signal.

  - **`expires_at`**

    `string`, format: `date-time`

  - **`label`**

    `string` — Human-readable label for this qURL

  - **`qurl_id`**

    `string` — Unique qURL identifier (q\_ + first 11 hex chars of token hash). Used as NHP resource ID and qurl\_site subdomain. Each qURL gets its own firewall rules keyed by this ID.

  - **`qurl_link`**

    `string`, format: `uri` — Ephemeral access link (shown once, cannot be recovered)

  - **`qurl_site`**

    `string`, format: `uri` — Per-qURL site URL (https\://{qurl\_id}.qurl.site)

  - **`resource_id`**

    `string` — Parent resource ID (auto-created grouping by target URL)

  - **`type`**

    `string` — Resource type — echoes the type from the create request.

  - **`upstream_addr`**

    `string` — Internal use.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "qurl_id": "q_3a7f2c8e91b",
    "resource_id": "",
    "qurl_link": "",
    "branded_domain": "",
    "qurl_site": "",
    "expires_at": "",
    "label": "",
    "type": "",
    "upstream_addr": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List qURLs

- **Method:** `GET`
- **Path:** `/v1/qurls`
- **Tags:** QURLs

Lists resources owned by the authenticated user. Each resource groups qURLs sharing the same target URL. Supports filtering by status and date ranges, with cursor-based pagination.

#### Responses

##### Status: 200 List of qURLs

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`created_at`**

    `string`, format: `date-time` — When the qURL was created

  - **`custom_domain`**

    `string | null` — Custom domain associated with this qURL

  - **`description`**

    `string` — Human-readable description

  - **`expires_at`**

    `string`, format: `date-time` — When the qURL expires

  - **`qurl_count`**

    `integer` — Number of active qURLs (access tokens) for this resource

  - **`qurl_site`**

    `string`, format: `uri` — The qURL site URL for accessing this resource

  - **`qurls`**

    `array` — Per-qURL details. Present on single-get, omitted on list (too expensive).

    **Items:**

    - **`access_policy`**

      `object` — Access control policy for the qURL

      - **`ai_agent_policy`**

        `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

        - **`allow_categories`**

          `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

        - **`block_all`**

          `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

        - **`deny_categories`**

          `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`geo_allowlist`**

        `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`geo_denylist`**

        `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`ip_allowlist`**

        `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`ip_denylist`**

        `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`user_agent_allow_regex`**

        `string` — Regular expression to allow matching user agents (max 256 characters)

      - **`user_agent_deny_regex`**

        `string` — Regular expression to deny matching user agents (max 256 characters)

    - **`created_at`**

      `string`, format: `date-time`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`label`**

      `string`

    - **`max_sessions`**

      `integer`

    - **`one_time_use`**

      `boolean`

    - **`qurl_id`**

      `string`

    - **`qurl_site`**

      `string`

    - **`session_duration`**

      `integer` — Session lifetime in seconds (0 = server default)

    - **`status`**

      `string`, possible values: `"active", "consumed", "expired", "revoked"`

    - **`use_count`**

      `integer`

  - **`resource_id`**

    `string` — Unique resource identifier (generated by server)

  - **`status`**

    `string`, possible values: `"active", "revoked"` — Current lifecycle status. Computed at response time — resources past their expires\_at are reported as "expired" even if not explicitly revoked.

  - **`tags`**

    `array` — Tags for categorization and filtering

    **Items:**

    `string`

  - **`target_url`**

    `string`, format: `uri` — The protected backend URL. Optional — omitted (field absent from the JSON object, NOT serialized as null) when the resource is owned by a connector.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "resource_id": "",
      "target_url": "",
      "status": "active",
      "created_at": "",
      "expires_at": "",
      "description": "",
      "tags": [
        ""
      ],
      "qurl_site": "",
      "custom_domain": null,
      "qurl_count": 1,
      "qurls": [
        {
          "qurl_id": "",
          "label": "",
          "status": "active",
          "one_time_use": true,
          "max_sessions": 1,
          "session_duration": 1,
          "use_count": 1,
          "qurl_site": "",
          "created_at": "",
          "expires_at": "",
          "...": "[Additional Properties Truncated]"
        }
      ]
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

##### Status: 304 Not Modified (ETag match)

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Get a qURL

- **Method:** `GET`
- **Path:** `/v1/qurls/{id}`
- **Tags:** QURLs

Retrieves a resource and its qURL tokens by resource or qURL display ID.

#### Responses

##### Status: 200 qURL details

###### Content-Type: application/json

- **`data`**

  `object` — Resource container for a protected URL. The \`qurls\` array (present on single-get responses) contains per-qURL details including access policy and session limits. On list responses, only \`qurl\_count\` is populated.

  - **`created_at`**

    `string`, format: `date-time` — When the qURL was created

  - **`custom_domain`**

    `string | null` — Custom domain associated with this qURL

  - **`description`**

    `string` — Human-readable description

  - **`expires_at`**

    `string`, format: `date-time` — When the qURL expires

  - **`qurl_count`**

    `integer` — Number of active qURLs (access tokens) for this resource

  - **`qurl_site`**

    `string`, format: `uri` — The qURL site URL for accessing this resource

  - **`qurls`**

    `array` — Per-qURL details. Present on single-get, omitted on list (too expensive).

    **Items:**

    - **`access_policy`**

      `object` — Access control policy for the qURL

      - **`ai_agent_policy`**

        `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

        - **`allow_categories`**

          `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

        - **`block_all`**

          `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

        - **`deny_categories`**

          `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`geo_allowlist`**

        `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`geo_denylist`**

        `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`ip_allowlist`**

        `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`ip_denylist`**

        `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`user_agent_allow_regex`**

        `string` — Regular expression to allow matching user agents (max 256 characters)

      - **`user_agent_deny_regex`**

        `string` — Regular expression to deny matching user agents (max 256 characters)

    - **`created_at`**

      `string`, format: `date-time`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`label`**

      `string`

    - **`max_sessions`**

      `integer`

    - **`one_time_use`**

      `boolean`

    - **`qurl_id`**

      `string`

    - **`qurl_site`**

      `string`

    - **`session_duration`**

      `integer` — Session lifetime in seconds (0 = server default)

    - **`status`**

      `string`, possible values: `"active", "consumed", "expired", "revoked"`

    - **`use_count`**

      `integer`

  - **`resource_id`**

    `string` — Unique resource identifier (generated by server)

  - **`status`**

    `string`, possible values: `"active", "revoked"` — Current lifecycle status. Computed at response time — resources past their expires\_at are reported as "expired" even if not explicitly revoked.

  - **`tags`**

    `array` — Tags for categorization and filtering

    **Items:**

    `string`

  - **`target_url`**

    `string`, format: `uri` — The protected backend URL. Optional — omitted (field absent from the JSON object, NOT serialized as null) when the resource is owned by a connector.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "resource_id": "",
    "target_url": "",
    "status": "active",
    "created_at": "",
    "expires_at": "",
    "description": "",
    "tags": [
      ""
    ],
    "qurl_site": "",
    "custom_domain": null,
    "qurl_count": 1,
    "qurls": [
      {
        "qurl_id": "",
        "label": "",
        "status": "active",
        "one_time_use": true,
        "max_sessions": 1,
        "session_duration": 1,
        "use_count": 1,
        "qurl_site": "",
        "created_at": "",
        "expires_at": "",
        "...": "[Additional Properties Truncated]"
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 304 Not Modified (ETag match)

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Delete a qURL

- **Method:** `DELETE`
- **Path:** `/v1/qurls/{id}`
- **Tags:** QURLs

Revokes a resource and all its qURLs. Requires a resource ID (r\_ prefix). To revoke a single token, use DELETE /v1/resources/{resource\_id}/qurls/{qurl\_id}.

#### Responses

##### Status: 204 qURL deleted successfully

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Update a qURL

- **Method:** `PATCH`
- **Path:** `/v1/qurls/{id}`
- **Tags:** QURLs

Updates resource-level metadata (description, tags, expiration).

#### Request Body

##### Content-Type: application/json

- **`description`**

  `string` — Replace the resource description. Pass an empty string to clear.

- **`expires_at`**

  `string`, format: `date-time` — Absolute expiration time. Mutually exclusive with extend\_by. Must be in the future and within 30 days from now.

- **`extend_by`**

  `string` — Duration to extend by (e.g., "24h", "7d", "1w"). Minimum: 1 minute. Maximum: 30 days. Mutually exclusive with expires\_at.

- **`tags`**

  `array` — Replace all tags on this resource. Tags are lowercased and trimmed server-side. Duplicates are silently removed. Pass an empty array to clear all tags.

  **Items:**

  `string`

**Example:**

```json
{
  "extend_by": "7d",
  "expires_at": "",
  "tags": [
    ""
  ],
  "description": ""
}
```

#### Responses

##### Status: 200 qURL updated successfully

###### Content-Type: application/json

- **`data`**

  `object` — Resource container for a protected URL. The \`qurls\` array (present on single-get responses) contains per-qURL details including access policy and session limits. On list responses, only \`qurl\_count\` is populated.

  - **`created_at`**

    `string`, format: `date-time` — When the qURL was created

  - **`custom_domain`**

    `string | null` — Custom domain associated with this qURL

  - **`description`**

    `string` — Human-readable description

  - **`expires_at`**

    `string`, format: `date-time` — When the qURL expires

  - **`qurl_count`**

    `integer` — Number of active qURLs (access tokens) for this resource

  - **`qurl_site`**

    `string`, format: `uri` — The qURL site URL for accessing this resource

  - **`qurls`**

    `array` — Per-qURL details. Present on single-get, omitted on list (too expensive).

    **Items:**

    - **`access_policy`**

      `object` — Access control policy for the qURL

      - **`ai_agent_policy`**

        `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

        - **`allow_categories`**

          `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

        - **`block_all`**

          `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

        - **`deny_categories`**

          `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`geo_allowlist`**

        `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`geo_denylist`**

        `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`ip_allowlist`**

        `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`ip_denylist`**

        `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`user_agent_allow_regex`**

        `string` — Regular expression to allow matching user agents (max 256 characters)

      - **`user_agent_deny_regex`**

        `string` — Regular expression to deny matching user agents (max 256 characters)

    - **`created_at`**

      `string`, format: `date-time`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`label`**

      `string`

    - **`max_sessions`**

      `integer`

    - **`one_time_use`**

      `boolean`

    - **`qurl_id`**

      `string`

    - **`qurl_site`**

      `string`

    - **`session_duration`**

      `integer` — Session lifetime in seconds (0 = server default)

    - **`status`**

      `string`, possible values: `"active", "consumed", "expired", "revoked"`

    - **`use_count`**

      `integer`

  - **`resource_id`**

    `string` — Unique resource identifier (generated by server)

  - **`status`**

    `string`, possible values: `"active", "revoked"` — Current lifecycle status. Computed at response time — resources past their expires\_at are reported as "expired" even if not explicitly revoked.

  - **`tags`**

    `array` — Tags for categorization and filtering

    **Items:**

    `string`

  - **`target_url`**

    `string`, format: `uri` — The protected backend URL. Optional — omitted (field absent from the JSON object, NOT serialized as null) when the resource is owned by a connector.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "resource_id": "",
    "target_url": "",
    "status": "active",
    "created_at": "",
    "expires_at": "",
    "description": "",
    "tags": [
      ""
    ],
    "qurl_site": "",
    "custom_domain": null,
    "qurl_count": 1,
    "qurls": [
      {
        "qurl_id": "",
        "label": "",
        "status": "active",
        "one_time_use": true,
        "max_sessions": 1,
        "session_duration": 1,
        "use_count": 1,
        "qurl_site": "",
        "created_at": "",
        "expires_at": "",
        "...": "[Additional Properties Truncated]"
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Mint an access link

- **Method:** `POST`
- **Path:** `/v1/qurls/{id}/mint_link`
- **Tags:** QURLs

Creates a new qURL access token for an existing resource. The link can be shared with users who need temporary access to the resource.

#### Request Body

##### Content-Type: application/json

- **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

- **`expires_at`**

  `string`, format: `date-time` — Absolute expiration timestamp. Must be in the future and within 30 days from now. Mutually exclusive with expires\_in.

- **`expires_in`**

  `string` — Duration until expiration. Supports: minutes (5m), hours (24h), days (7d), weeks (1w). Minimum: 1 minute. Maximum: 30 days.

- **`label`**

  `string` — Human-readable label identifying who this qURL is for

- **`max_sessions`**

  `integer`, default: `0` — Maximum concurrent sessions (0 = unlimited)

- **`one_time_use`**

  `boolean`, default: `false` — Whether this qURL can only be used once

- **`session_duration`**

  `string` — How long access lasts after clicking. Minimum: 1 second. Maximum: 24 hours.

**Example:**

```json
{
  "expires_in": "5m",
  "expires_at": "",
  "label": "Alice from Acme",
  "one_time_use": false,
  "max_sessions": 0,
  "session_duration": "1h",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  }
}
```

#### Responses

##### Status: 201 Access link created

###### Content-Type: application/json

- **`data`**

  `object`

  - **`branded_domain`**

    `string` — Customer's bare branded hostname (e.g. \`customer.com\`) for anchor-text display alongside \`qurl\_link\`. Render as \`\<a href="{qurl\_link}">{branded\_domain}\</a>\` — the access token lives in \`qurl\_link\`'s fragment, not here, so the href must stay \`qurl\_link\`. Populated when the resource has a \`custom\_domain\` whose status is usable (\`verified\`, \`provisioning\_tls\`, or \`active\`) and is owned by the requesting tenant; omitted when no custom domain is set, when the assigned domain has transitioned to a non-usable status, or when the requesting tenant doesn't own the live domain row (defends against domain churn — e.g. a domain deleted and re-registered by a different tenant). During \`provisioning\_tls\` the branded host may not yet be serving HTTPS, so treat the value as a display string until the domain reaches \`active\`.

  - **`expires_at`**

    `string`, format: `date-time`

  - **`qurl_id`**

    `string` — The minted qURL's primary key (prefix \`q\_\`). Same identifier as \`CreateQurlData.qurl\_id\`. Webhook subscribers receive \`qurl.accessed\` events keyed on this ID, so callers that want to correlate access events back to the recipient/context they minted FOR must capture this value at mint time.

  - **`qurl_link`**

    `string`, format: `uri` — Single-use access link

  - **`type`**

    `string` — Resource type — echoes the type of the underlying resource.

  - **`upstream_addr`**

    `string` — Internal use.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "qurl_id": "q_a1b2c3d4e5f",
    "qurl_link": "",
    "branded_domain": "",
    "expires_at": "",
    "type": "",
    "upstream_addr": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Batch create qURLs

- **Method:** `POST`
- **Path:** `/v1/qurls/batch`
- **Tags:** QURLs

Creates multiple qURL resources in a single request. Returns 201 if all succeed, 207 Multi-Status if mixed results, or 400 if all fail.

**Validation is atomic:** if any item fails input validation (e.g. invalid tags, description too long), the entire batch is rejected with per-item error details. Items that passed validation are marked as "skipped" in the results.

#### Request Body

##### Content-Type: application/json

- **`items` (required)**

  `array` — Array of qURL creation requests (1-100 items)

  **Items:**

  - **`access_policy`**

    `object` — Access control policy for the qURL

    - **`ai_agent_policy`**

      `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

      - **`allow_categories`**

        `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`block_all`**

        `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

      - **`deny_categories`**

        `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`geo_allowlist`**

      `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`geo_denylist`**

      `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`ip_allowlist`**

      `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`ip_denylist`**

      `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`user_agent_allow_regex`**

      `string` — Regular expression to allow matching user agents (max 256 characters)

    - **`user_agent_deny_regex`**

      `string` — Regular expression to deny matching user agents (max 256 characters)

  - **`custom_domain`**

    `string` — Optional custom domain to assign to the auto-created resource. The domain must be registered, active, and owned by the caller. Only allowed when the target URL creates a new resource — rejected if a resource already exists for this target URL.

  - **`expires_in`**

    `string` — Duration until expiration. Supports: hours (24h), days (7d), weeks (1w). Minimum: 1 minute. Maximum varies by plan: free=3 days, growth/enterprise=30 days. Default: 24h.

  - **`label`**

    `string` — Human-readable label identifying who this qURL is for

  - **`max_sessions`**

    `integer` — Maximum concurrent sessions allowed. 0 = unlimited (default), 1-1000 = hard limit.

  - **`one_time_use`**

    `boolean`, default: `false` — Whether this qURL expires after a single use

  - **`session_duration`**

    `string` — How long access lasts after someone clicks this qURL. Controls the session/cookie lifetime, separate from the qURL link expiration (expires\_in). Supports: seconds (30s), minutes (5m), hours (1h), days (1d). Minimum: 1 second. Maximum: 24 hours. Default: server default (typically 1 hour).

  - **`target_url`**

    `string`, format: `uri` — The URL to protect with qURL (max 2048 characters). Required for standard reverse-proxy qURLs.

  - **`type`**

    `string`, default: `"url"` — Resource type. The handler accepts only \`url\` on this public surface; see the ResourceType schema for the full runtime enum.

**Example:**

```json
{
  "items": [
    {
      "type": "url",
      "target_url": "https://internal.example.com/dashboard",
      "expires_in": "7d",
      "one_time_use": false,
      "max_sessions": 0,
      "session_duration": "1h",
      "access_policy": {
        "ip_allowlist": [
          "192.168.1.0/24",
          "10.0.0.1"
        ],
        "ip_denylist": [
          ""
        ],
        "geo_allowlist": [
          "US",
          "CA",
          "GB"
        ],
        "geo_denylist": [
          "CN",
          "RU"
        ],
        "user_agent_allow_regex": "^Mozilla.*",
        "user_agent_deny_regex": ".*bot.*",
        "ai_agent_policy": {
          "block_all": false,
          "deny_categories": [
            "gptbot",
            "commoncrawl",
            "bytedance"
          ],
          "allow_categories": [
            "claude",
            "chatgpt",
            "perplexity"
          ]
        }
      },
      "label": "Alice from Acme",
      "custom_domain": "app.example.com"
    }
  ]
}
```

#### Responses

##### Status: 201 All qURLs created successfully

###### Content-Type: application/json

- **`data`**

  `object`

  - **`failed`**

    `integer` — Number of failed creations

  - **`results`**

    `array`

    **Items:**

    - **`branded_domain`**

      `string` — Customer's bare branded hostname (if success). See \`MintLinkData.branded\_domain\` for the canonical description, including the \`\<a href="{qurl\_link}">{branded\_domain}\</a>\` rendering pattern, the \`provisioning\_tls\` HTTPS-not-yet-live caveat, and the live-status + tenant-ownership gating.

    - **`error`**

      `object` — Error details (if failed)

      - **`code`**

        `string`

      - **`message`**

        `string`

    - **`expires_at`**

      `string`, format: `date-time` — Expiration time (if success)

    - **`index`**

      `integer` — Original index in the request array

    - **`qurl_link`**

      `string`, format: `uri` — Access link (if success)

    - **`qurl_site`**

      `string`, format: `uri` — qURL site URL (if success)

    - **`resource_id`**

      `string` — Created resource ID (if success)

    - **`success`**

      `boolean`

  - **`succeeded`**

    `integer` — Number of successfully created qURLs

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "succeeded": 1,
    "failed": 1,
    "results": [
      {
        "index": 1,
        "success": true,
        "resource_id": "",
        "qurl_link": "",
        "branded_domain": "",
        "qurl_site": "",
        "expires_at": "",
        "error": {
          "code": "",
          "message": ""
        }
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 207 Mixed results (some succeeded, some failed)

###### Content-Type: application/json

- **`data`**

  `object`

  - **`failed`**

    `integer` — Number of failed creations

  - **`results`**

    `array`

    **Items:**

    - **`branded_domain`**

      `string` — Customer's bare branded hostname (if success). See \`MintLinkData.branded\_domain\` for the canonical description, including the \`\<a href="{qurl\_link}">{branded\_domain}\</a>\` rendering pattern, the \`provisioning\_tls\` HTTPS-not-yet-live caveat, and the live-status + tenant-ownership gating.

    - **`error`**

      `object` — Error details (if failed)

      - **`code`**

        `string`

      - **`message`**

        `string`

    - **`expires_at`**

      `string`, format: `date-time` — Expiration time (if success)

    - **`index`**

      `integer` — Original index in the request array

    - **`qurl_link`**

      `string`, format: `uri` — Access link (if success)

    - **`qurl_site`**

      `string`, format: `uri` — qURL site URL (if success)

    - **`resource_id`**

      `string` — Created resource ID (if success)

    - **`success`**

      `boolean`

  - **`succeeded`**

    `integer` — Number of successfully created qURLs

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "succeeded": 1,
    "failed": 1,
    "results": [
      {
        "index": 1,
        "success": true,
        "resource_id": "",
        "qurl_link": "",
        "branded_domain": "",
        "qurl_site": "",
        "expires_at": "",
        "error": {
          "code": "",
          "message": ""
        }
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 All items failed or invalid request

###### Content-Type: application/json

- **`data`**

  `object`

  - **`failed`**

    `integer` — Number of failed creations

  - **`results`**

    `array`

    **Items:**

    - **`branded_domain`**

      `string` — Customer's bare branded hostname (if success). See \`MintLinkData.branded\_domain\` for the canonical description, including the \`\<a href="{qurl\_link}">{branded\_domain}\</a>\` rendering pattern, the \`provisioning\_tls\` HTTPS-not-yet-live caveat, and the live-status + tenant-ownership gating.

    - **`error`**

      `object` — Error details (if failed)

      - **`code`**

        `string`

      - **`message`**

        `string`

    - **`expires_at`**

      `string`, format: `date-time` — Expiration time (if success)

    - **`index`**

      `integer` — Original index in the request array

    - **`qurl_link`**

      `string`, format: `uri` — Access link (if success)

    - **`qurl_site`**

      `string`, format: `uri` — qURL site URL (if success)

    - **`resource_id`**

      `string` — Created resource ID (if success)

    - **`success`**

      `boolean`

  - **`succeeded`**

    `integer` — Number of successfully created qURLs

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "succeeded": 1,
    "failed": 1,
    "results": [
      {
        "index": 1,
        "success": true,
        "resource_id": "",
        "qurl_link": "",
        "branded_domain": "",
        "qurl_site": "",
        "expires_at": "",
        "error": {
          "code": "",
          "message": ""
        }
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List resources

- **Method:** `GET`
- **Path:** `/v1/resources`
- **Tags:** Resources

Lists resources (target URL groupings) for the authenticated user. Each resource groups qURLs that share the same target URL. The response includes a `qurl_count` for each resource showing how many active qURLs exist for it. Use GET /v1/resources/{id} to see the individual qURLs.

**Filtering by alias:** pass `?alias={alias}` to look up a specific owner-scoped alias. Returns a list of 0 or 1 items — the list shape is preserved so the endpoint composes with future filters. An empty list means the caller does not own a resource with that alias (tenant-isolated: aliases for other owners are not visible). Reserved-word and format errors on the `alias` query surface as `400 alias_reserved` / `400 alias_invalid_format`. When `alias` is supplied, `cursor` / `limit` are ignored (the result is bounded by the per-owner alias uniqueness invariant).

#### Responses

##### Status: 200 Resource list

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`alias`**

    `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

  - **`created_at`**

    `string`, format: `date-time`

  - **`custom_domain`**

    `string | null`

  - **`description`**

    `string`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`preserve_host`**

    `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

  - **`qurl_count`**

    `integer` — Number of active qURLs for this resource

  - **`resource_id`**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`tags`**

    `array`

    **Items:**

    `string`

  - **`target_url`**

    `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

  - **`type`**

    `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

  - **`upstream_addr`**

    `string` — Internal use.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "resource_id": "",
      "type": "",
      "target_url": "",
      "upstream_addr": "",
      "status": "active",
      "description": "",
      "tags": [
        ""
      ],
      "custom_domain": null,
      "alias": null,
      "preserve_host": false,
      "qurl_count": 1,
      "created_at": "",
      "expires_at": ""
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Create resource

- **Method:** `POST`
- **Path:** `/v1/resources`
- **Tags:** Resources

Explicitly create a resource (register a target URL before creating qURLs for it). If a resource already exists for the same target URL and owner, the existing resource is returned (idempotent).

Alias semantics on POST (full rules in the `CreateResourceRequest` schema description):

- New resource + new alias: creates and binds.
- Idempotent: same target URL + matching alias (or no alias) returns the existing resource.
- 409 `resource_alias_mismatch`: same target URL but the request supplies an alias that disagrees with the existing binding (including "supplies an alias when the existing resource has none"). Remediation: use `PATCH /v1/resources/{id}` with `alias` to set or rebind.
- 409 `alias_in_use`: the alias is already claimed by another of your resources (sentinel collision). Remediation: pick a different alias.

#### Request Body

##### Content-Type: application/json

- **`alias`**

  `string` — Optional human-readable handle for the resource, unique per owner. Format: 3–64 lowercase alphanumeric chars and hyphens, must start with a letter and end alphanumeric. Reserved words (e.g. \`admin\`, \`qurl\`, \`tunnel\`) are rejected with 400 \`alias\_reserved\`. Collisions with another of your resources return 409 \`alias\_in\_use\`. Disagreeing with the alias of an existing resource for the same target URL returns 409 \`resource\_alias\_mismatch\`.

- **`custom_domain`**

  `string` — Optional custom domain to bind to this resource. Must be a domain previously verified via the domains API.

- **`description`**

  `string`

- **`tags`**

  `array`

  **Items:**

  `string`

- **`target_url`**

  `string`, format: `uri` — The URL to protect.

- **`type`**

  `string`, possible values: `"url", "tunnel", "transit"`, default: `"url"` — Resource type. \`url\` is a target-URL proxy (default). \`tunnel\` is a reverse-tunnel resource for LayerV-provisioned tunnel callers. \`transit\` is connector-owned (user-uploaded content via integrations). See \`ResourceData.type\` for the read-side redaction contract that applies on management-API read responses for non-\`url\` rows.

**Example:**

```json
{
  "type": "url",
  "target_url": "",
  "description": "",
  "tags": [
    ""
  ],
  "custom_domain": "",
  "alias": "dev-dashboard"
}
```

#### Responses

##### Status: 201 Resource created

###### Content-Type: application/json

- **`data`**

  `object`

  - **`alias`**

    `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

  - **`created_at`**

    `string`, format: `date-time`

  - **`custom_domain`**

    `string | null`

  - **`description`**

    `string`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`preserve_host`**

    `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

  - **`qurl_count`**

    `integer` — Number of active qURLs for this resource

  - **`resource_id`**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`tags`**

    `array`

    **Items:**

    `string`

  - **`target_url`**

    `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

  - **`type`**

    `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

  - **`upstream_addr`**

    `string` — Internal use.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "resource_id": "",
    "type": "",
    "target_url": "",
    "upstream_addr": "",
    "status": "active",
    "description": "",
    "tags": [
      ""
    ],
    "custom_domain": null,
    "alias": null,
    "preserve_host": false,
    "qurl_count": 1,
    "created_at": "",
    "expires_at": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 409 Returned in two alias scenarios with distinct error codes: - \`alias\_in\_use\`: the alias is already claimed by another resource owned by the caller (sentinel collision). Remediation: pick a different alias, or release the conflict via \`PATCH alias: null\` / \`DELETE\` on the owning resource. - \`resource\_alias\_mismatch\`: the request's \`target\_url\` resolves to an existing resource whose alias disagrees with the request (or that has no alias while the request supplies one). The endpoint refuses to silently rebind on what looks like an idempotent create. Remediation: rebind explicitly via \`PATCH /v1/resources/{id}\`.

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### PARAMETERS /v1/resources/{id}

- **Method:** `PARAMETERS`
- **Path:** `/v1/resources/{id}`

### Get resource details

- **Method:** `GET`
- **Path:** `/v1/resources/{id}`
- **Tags:** Resources

Get resource details including all qURLs associated with it. Each qURL has its own label, expiry, access policy, and NHP firewall rules — independent of other qURLs on the same resource.

#### Responses

##### Status: 200 Resource details

###### Content-Type: application/json

- **`data`**

  `object`

  - **`qurls`**

    `array`

    **Items:**

    - **`access_policy`**

      `object` — Access control policy for the qURL

      - **`ai_agent_policy`**

        `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

        - **`allow_categories`**

          `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

        - **`block_all`**

          `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

        - **`deny_categories`**

          `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`geo_allowlist`**

        `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`geo_denylist`**

        `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`ip_allowlist`**

        `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`ip_denylist`**

        `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`user_agent_allow_regex`**

        `string` — Regular expression to allow matching user agents (max 256 characters)

      - **`user_agent_deny_regex`**

        `string` — Regular expression to deny matching user agents (max 256 characters)

    - **`created_at`**

      `string`, format: `date-time`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`label`**

      `string`

    - **`max_sessions`**

      `integer`

    - **`one_time_use`**

      `boolean`

    - **`qurl_id`**

      `string`

    - **`qurl_site`**

      `string`

    - **`session_duration`**

      `integer` — Session lifetime in seconds (0 = server default)

    - **`status`**

      `string`, possible values: `"active", "consumed", "expired", "revoked"`

    - **`use_count`**

      `integer`

  - **`resource`**

    `object`

    - **`alias`**

      `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

    - **`created_at`**

      `string`, format: `date-time`

    - **`custom_domain`**

      `string | null`

    - **`description`**

      `string`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`preserve_host`**

      `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

    - **`qurl_count`**

      `integer` — Number of active qURLs for this resource

    - **`resource_id`**

      `string`

    - **`status`**

      `string`, possible values: `"active", "revoked"`

    - **`tags`**

      `array`

      **Items:**

      `string`

    - **`target_url`**

      `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

    - **`type`**

      `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

    - **`upstream_addr`**

      `string` — Internal use.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "resource": {
      "resource_id": "",
      "type": "",
      "target_url": "",
      "upstream_addr": "",
      "status": "active",
      "description": "",
      "tags": [
        ""
      ],
      "custom_domain": null,
      "alias": null,
      "preserve_host": false,
      "qurl_count": 1,
      "created_at": "",
      "expires_at": ""
    },
    "qurls": [
      {
        "qurl_id": "",
        "label": "",
        "status": "active",
        "one_time_use": true,
        "max_sessions": 1,
        "session_duration": 1,
        "use_count": 1,
        "qurl_site": "",
        "created_at": "",
        "expires_at": "",
        "access_policy": {
          "ip_allowlist": [
            "192.168.1.0/24",
            "10.0.0.1"
          ],
          "ip_denylist": [
            ""
          ],
          "geo_allowlist": [
            "US",
            "CA",
            "GB"
          ],
          "geo_denylist": [
            "CN",
            "RU"
          ],
          "user_agent_allow_regex": "^Mozilla.*",
          "user_agent_deny_regex": ".*bot.*",
          "ai_agent_policy": {
            "block_all": false,
            "deny_categories": [
              "gptbot",
              "commoncrawl",
              "bytedance"
            ],
            "allow_categories": [
              "claude",
              "chatgpt",
              "perplexity"
            ]
          }
        }
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Update resource metadata

- **Method:** `PATCH`
- **Path:** `/v1/resources/{id}`
- **Tags:** Resources

Update resource metadata (tags, description, preserve\_host, custom\_domain, alias).

**Alias semantics (RFC 7396 JSON Merge Patch):** the `alias` field is tri-valued — omit for no change, send a string to set or rebind, send `null` to clear the existing alias and release the uniqueness sentinel.

**Atomicity:** all field writes within a single PATCH commit atomically — the resource never lands in a partial state where some fields applied and others did not. The alias-mutation path is the implementation tripwire: an alias write touches two rows (the resource row and a `(owner_id, alias)` uniqueness-sentinel row), and both writes — plus any `tags` / `description` / `preserve_host` fields in the same body — bundle into a single `TransactWriteItems` so a `409 alias_in_use` rolls the whole PATCH back.

**`custom_domain` exclusion:** `custom_domain` set/clear runs external DNS verification and a uniqueness query that don't fold into the alias transaction. To preserve atomicity, `custom_domain` and any alias mutation are mutually exclusive in the same PATCH (returns `400 mutually_exclusive_fields`). Issue the two mutations as separate PATCH requests when both are needed.

#### Request Body

##### Content-Type: application/json

- **`alias`**

  `string | null` — Set, rebind, or clear the resource's alias under RFC 7396 JSON Merge Patch semantics: - omitted → no change. - \`null\` → clear the alias and release the uniqueness sentinel. Equivalent to deleting the alias. - string value → set or rebind. Pattern, length, and reserved-word rules match \`CreateResourceRequest.alias\`. Sending an empty string (\`""\`) is rejected with \`400 alias\_invalid\_format\` to avoid the silent-no-op trap on callers building this body from user input — use \`null\` for the clear directive.

- **`custom_domain`**

  `string`

- **`description`**

  `string`

- **`preserve_host`**

  `boolean` — Whether to preserve the original Host header when proxying to the origin via a custom domain. When true, the custom domain is sent as the Host header. When false (default), the target server's hostname is used. Only meaningful when custom\_domain is set.

- **`tags`**

  `array`

  **Items:**

  `string`

**Example:**

```json
{
  "description": "",
  "tags": [
    ""
  ],
  "custom_domain": "",
  "preserve_host": true,
  "alias": null
}
```

#### Responses

##### Status: 200 Resource updated

###### Content-Type: application/json

- **`data`**

  `object`

  - **`alias`**

    `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

  - **`created_at`**

    `string`, format: `date-time`

  - **`custom_domain`**

    `string | null`

  - **`description`**

    `string`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`preserve_host`**

    `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

  - **`qurl_count`**

    `integer` — Number of active qURLs for this resource

  - **`resource_id`**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`tags`**

    `array`

    **Items:**

    `string`

  - **`target_url`**

    `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

  - **`type`**

    `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

  - **`upstream_addr`**

    `string` — Internal use.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "resource_id": "",
    "type": "",
    "target_url": "",
    "upstream_addr": "",
    "status": "active",
    "description": "",
    "tags": [
      ""
    ],
    "custom_domain": null,
    "alias": null,
    "preserve_host": false,
    "qurl_count": 1,
    "created_at": "",
    "expires_at": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 409 Alias already in use by another resource. The entire PATCH is rolled back — all fields sent in the same request are NOT persisted.

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Revoke resource and all its qURLs

- **Method:** `DELETE`
- **Path:** `/v1/resources/{id}`
- **Tags:** Resources

Revokes a resource. All qURLs associated with the resource become inaccessible because token resolution checks the resource status.

#### Responses

##### Status: 204 Resource revoked

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### PARAMETERS /v1/resources/{id}/qurls

- **Method:** `PARAMETERS`
- **Path:** `/v1/resources/{id}/qurls`

### Mint a qURL against an existing resource

- **Method:** `POST`
- **Path:** `/v1/resources/{id}/qurls`
- **Tags:** Resources

Creates a new qURL against the resource identified by `{id}`. Use this endpoint when you already have a resource ID; use `POST /v1/qurls` to create a qURL from a `target_url` directly.

A request body is required, but every field inside it is optional — to mint with the owner's plan defaults, send `{}`. (The body is required at the wire level so empty `POST`s are rejected with a clear validation error rather than silently succeeding.)

Status hierarchy:

- 404 `resource_not_found` if no resource with this ID exists.
- 403 `access_denied` if the resource exists but belongs to another owner.
- 403 `tunnel_disabled` if the resource is a tunnel and tunnel minting is currently unavailable.
- 400 `revoked` if the resource is revoked.
- 400 `invalid_input` for fields that don't apply to the resource's type (e.g. `max_sessions` on a tunnel resource).

#### Request Body

##### Content-Type: application/json

- **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

- **`expires_in`**

  `string` — Duration until expiration. Supports: hours (24h), days (7d), weeks (1w). Minimum: 1 minute. Maximum varies by plan: free=3 days, growth/enterprise=30 days. Default: 24h.

- **`label`**

  `string` — Human-readable label identifying who this qURL is for.

- **`max_sessions`**

  `integer` — Maximum concurrent sessions allowed (URL resources only). 0 = unlimited (default), 1-1000 = hard limit.

- **`one_time_use`**

  `boolean`, default: `false` — Whether this qURL expires after a single use. URL resources only.

- **`session_duration`**

  `string` — How long access lasts after someone clicks this qURL. Controls the session/cookie lifetime, separate from the qURL link expiration (expires\_in). Supports: seconds (30s), minutes (5m), hours (1h), days (1d). Minimum: 1 second. Maximum: 24 hours. Default: server default (typically 1 hour). URL resources only.

**Example:**

```json
{
  "expires_in": "7d",
  "one_time_use": false,
  "max_sessions": 0,
  "session_duration": "1h",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  },
  "label": "Alice from Acme"
}
```

#### Responses

##### Status: 201 qURL created successfully

###### Content-Type: application/json

- **`data`**

  `object` — Response data for qURL creation

  - **`branded_domain`**

    `string` — Customer's bare branded hostname for anchor-text display alongside \`qurl\_link\`. See \`MintLinkData.branded\_domain\` for the canonical description (rendering pattern, \`provisioning\_tls\` caveat, live-status gating). Populated when the resource has a \`custom\_domain\` whose status is usable; omitted otherwise. Note: requests replayed via \`Idempotency-Key\` return the cached response, so \`branded\_domain\` reflects the domain status at the time of the original request — not the current status. Consumers caching create responses should re-fetch via the resource if they need a live usability signal.

  - **`expires_at`**

    `string`, format: `date-time`

  - **`label`**

    `string` — Human-readable label for this qURL

  - **`qurl_id`**

    `string` — Unique qURL identifier (q\_ + first 11 hex chars of token hash). Used as NHP resource ID and qurl\_site subdomain. Each qURL gets its own firewall rules keyed by this ID.

  - **`qurl_link`**

    `string`, format: `uri` — Ephemeral access link (shown once, cannot be recovered)

  - **`qurl_site`**

    `string`, format: `uri` — Per-qURL site URL (https\://{qurl\_id}.qurl.site)

  - **`resource_id`**

    `string` — Parent resource ID (auto-created grouping by target URL)

  - **`type`**

    `string` — Resource type — echoes the type from the create request.

  - **`upstream_addr`**

    `string` — Internal use.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "qurl_id": "q_3a7f2c8e91b",
    "resource_id": "",
    "qurl_link": "",
    "branded_domain": "",
    "qurl_site": "",
    "expires_at": "",
    "label": "",
    "type": "",
    "upstream_addr": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### PARAMETERS /v1/resources/{id}/qurls/{qurl\_id}

- **Method:** `PARAMETERS`
- **Path:** `/v1/resources/{id}/qurls/{qurl_id}`

### Revoke a specific qURL

- **Method:** `DELETE`
- **Path:** `/v1/resources/{id}/qurls/{qurl_id}`
- **Tags:** Resources

Revokes a specific qURL token. The qURL must be active. Other qURLs on the same resource are not affected.

#### Responses

##### Status: 204 qURL revoked

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 409 qURL is not active

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Update a specific qURL

- **Method:** `PATCH`
- **Path:** `/v1/resources/{id}/qurls/{qurl_id}`
- **Tags:** Resources

Update expiry, label, access policy, or max\_sessions on a specific qURL token. The qURL must be active. At least one field must be provided.

#### Request Body

##### Content-Type: application/json

- **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

- **`expires_at`**

  `string`, format: `date-time` — Absolute expiration timestamp (RFC 3339). Must be in the future and within 30 days. Mutually exclusive with extend\_by.

- **`extend_by`**

  `string` — Duration to extend by (e.g., "24h", "7d"). Minimum: 1 minute. Maximum: 30 days. Mutually exclusive with expires\_at.

- **`label`**

  `string` — Human-readable label for this qURL.

- **`max_sessions`**

  `integer` — Maximum concurrent sessions (0 = unlimited).

- **`session_duration`**

  `string` — How long access lasts after clicking. Minimum: 1 second. Maximum: 24 hours.

**Example:**

```json
{
  "extend_by": "7d",
  "expires_at": "",
  "label": "",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  },
  "max_sessions": 0,
  "session_duration": "1h"
}
```

#### Responses

##### Status: 200 qURL updated

###### Content-Type: application/json

- **`data`**

  `object`

  - **`access_policy`**

    `object` — Access control policy for the qURL

    - **`ai_agent_policy`**

      `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

      - **`allow_categories`**

        `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`block_all`**

        `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

      - **`deny_categories`**

        `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`geo_allowlist`**

      `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`geo_denylist`**

      `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`ip_allowlist`**

      `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`ip_denylist`**

      `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`user_agent_allow_regex`**

      `string` — Regular expression to allow matching user agents (max 256 characters)

    - **`user_agent_deny_regex`**

      `string` — Regular expression to deny matching user agents (max 256 characters)

  - **`created_at`**

    `string`, format: `date-time`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`label`**

    `string`

  - **`max_sessions`**

    `integer`

  - **`one_time_use`**

    `boolean`

  - **`qurl_id`**

    `string`

  - **`qurl_site`**

    `string`

  - **`session_duration`**

    `integer` — Session lifetime in seconds (0 = server default)

  - **`status`**

    `string`, possible values: `"active", "consumed", "expired", "revoked"`

  - **`use_count`**

    `integer`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "qurl_id": "",
    "label": "",
    "status": "active",
    "one_time_use": true,
    "max_sessions": 1,
    "session_duration": 1,
    "use_count": 1,
    "qurl_site": "",
    "created_at": "",
    "expires_at": "",
    "access_policy": {
      "ip_allowlist": [
        "192.168.1.0/24",
        "10.0.0.1"
      ],
      "ip_denylist": [
        ""
      ],
      "geo_allowlist": [
        "US",
        "CA",
        "GB"
      ],
      "geo_denylist": [
        "CN",
        "RU"
      ],
      "user_agent_allow_regex": "^Mozilla.*",
      "user_agent_deny_regex": ".*bot.*",
      "ai_agent_policy": {
        "block_all": false,
        "deny_categories": [
          "gptbot",
          "commoncrawl",
          "bytedance"
        ],
        "allow_categories": [
          "claude",
          "chatgpt",
          "perplexity"
        ]
      }
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 409 qURL is not active

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List active sessions for a resource

- **Method:** `GET`
- **Path:** `/v1/resources/{id}/sessions`
- **Tags:** Sessions

Returns all active access sessions for the specified resource.

#### Responses

##### Status: 200 Active sessions

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`created_at`**

    `string`, format: `date-time`

  - **`last_seen_at`**

    `string`, format: `date-time`

  - **`qurl_id`**

    `string` — qURL display ID (q\_ prefix) that created this session

  - **`session_id`**

    `string` — Unique session identifier

  - **`src_ip`**

    `string` — Client IP that created the session

  - **`user_agent`**

    `string` — Client user agent

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": [
    {
      "session_id": "",
      "qurl_id": "",
      "src_ip": "",
      "user_agent": "",
      "created_at": "",
      "last_seen_at": ""
    }
  ],
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Terminate all active sessions for a resource

- **Method:** `DELETE`
- **Path:** `/v1/resources/{id}/sessions`
- **Tags:** Sessions

Immediately terminates all active sessions for the specified resource.

#### Responses

##### Status: 200 Sessions terminated

###### Content-Type: application/json

- **`data`**

  `object`

  - **`terminated`**

    `integer` — Number of sessions terminated

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "terminated": 1
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Terminate a specific session

- **Method:** `DELETE`
- **Path:** `/v1/resources/{id}/sessions/{session_id}`
- **Tags:** Sessions

Immediately terminates a specific active session.

#### Responses

##### Status: 204 Session terminated

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Get quota information

- **Method:** `GET`
- **Path:** `/v1/quota`
- **Tags:** Quota

Retrieves usage quota for the authenticated user.

#### Responses

##### Status: 200 Quota information

###### Content-Type: application/json

- **`data`**

  `object`

  - **`period_end`**

    `string`, format: `date-time`

  - **`period_start`**

    `string`, format: `date-time`

  - **`plan`**

    `string`, possible values: `"free", "growth", "enterprise"`

  - **`rate_limits`**

    `object`

    - **`create_per_hour`**

      `integer`

    - **`create_per_minute`**

      `integer`

    - **`list_per_minute`**

      `integer`

    - **`max_active_qurls`**

      `integer` — Maximum active qURLs allowed (-1 = unlimited)

    - **`max_expiry_seconds`**

      `integer` — Maximum expiry duration in seconds (free=259200/3d, growth=2592000/30d, enterprise=2592000/30d)

    - **`max_tokens_per_qurl`**

      `integer` — Maximum tokens per qURL (-1 = unlimited)

    - **`resolve_per_minute`**

      `integer` — Rate limit for token resolution requests

  - **`usage`**

    `object`

    - **`active_qurls`**

      `integer` — Currently active qURLs

    - **`active_qurls_percent`**

      `number | null`, format: `float` — Percentage of max\_active\_qurls used (0-100, null if unlimited)

    - **`qurls_created`**

      `integer` — Total qURLs created this period

    - **`total_accesses`**

      `integer` — Total access attempts this period

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "plan": "free",
    "period_start": "",
    "period_end": "",
    "rate_limits": {
      "create_per_minute": 1,
      "create_per_hour": 1,
      "list_per_minute": 1,
      "resolve_per_minute": 1,
      "max_active_qurls": 1,
      "max_tokens_per_qurl": 1,
      "max_expiry_seconds": 1
    },
    "usage": {
      "qurls_created": 1,
      "active_qurls": 1,
      "active_qurls_percent": null,
      "total_accesses": 1
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 304 Not Modified (ETag match)

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Get current billing period usage

- **Method:** `GET`
- **Path:** `/v1/usage/current-period`
- **Tags:** Usage

Returns usage statistics for the current billing period, including the number of qURLs created, the user's tier, period dates, and a cost estimate for usage-based plans (Growth tier). Requires JWT authentication (dashboard endpoint).

#### Responses

##### Status: 200 Current period usage information

###### Content-Type: application/json

- **`data`**

  `object`

  - **`active_qurls` (required)**

    `integer` — Number of currently active (non-expired, non-revoked) qURLs

  - **`period_end` (required)**

    `string`, format: `date-time` — End of the current billing period (UTC)

  - **`period_start` (required)**

    `string`, format: `date-time` — Start of the current billing period (UTC)

  - **`qurls_created` (required)**

    `integer` — Number of qURLs created in this period

  - **`tier` (required)**

    `string`, possible values: `"free", "growth", "enterprise"` — Current billing tier

  - **`cost_estimate`**

    `object`

    - **`amount_cents` (required)**

      `integer` — Cost in cents

    - **`currency` (required)**

      `string` — Currency code (e.g. "usd")

    - **`description` (required)**

      `string` — Human-readable breakdown

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "tier": "free",
    "period_start": "",
    "period_end": "",
    "qurls_created": 1,
    "active_qurls": 1,
    "cost_estimate": {
      "currency": "usd",
      "amount_cents": 1500,
      "description": "150 qURLs x $0.10/qURL"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Get daily usage breakdown

- **Method:** `GET`
- **Path:** `/v1/usage/daily`
- **Tags:** Usage

Returns a daily breakdown of qURL creation counts for the current billing period. Intended for dashboard chart rendering. Requires JWT authentication (dashboard endpoint).

#### Responses

##### Status: 200 Daily usage breakdown

###### Content-Type: application/json

- **`data`**

  `object`

  - **`daily` (required)**

    `array` — One entry per day from period\_start to today

    **Items:**

    - **`date` (required)**

      `string`, format: `date` — Calendar date (YYYY-MM-DD)

    - **`qurls_created` (required)**

      `integer` — qURLs created on this date

  - **`period_end` (required)**

    `string`, format: `date-time` — End of the current billing period (UTC)

  - **`period_start` (required)**

    `string`, format: `date-time` — Start of the current billing period (UTC)

  - **`tier` (required)**

    `string`, possible values: `"free", "growth", "enterprise"` — Current billing tier

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "tier": "free",
    "period_start": "",
    "period_end": "",
    "daily": [
      {
        "date": "",
        "qurls_created": 1
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Get customer profile

- **Method:** `GET`
- **Path:** `/v1/customer`
- **Tags:** Customer

Returns the authenticated user's customer profile including tier, spending cap, usage count, and frozen status. Creates a free-tier record on first access (lazy provisioning). Requires JWT authentication (dashboard endpoint). API key authentication is not supported.

#### Responses

##### Status: 200 Customer profile

###### Content-Type: application/json

- **`data`**

  `object`

  - **`current_period_usage` (required)**

    `integer`, format: `int64` — Usage count in current billing period

  - **`frozen` (required)**

    `boolean` — Whether the account is frozen

  - **`spending_cap_cents` (required)**

    `integer`, format: `int64` — Spending cap in cents (0 = no cap)

  - **`tier` (required)**

    `string`, possible values: `"free", "growth", "enterprise"` — Customer's billing tier

  - **`frozen_reason`**

    `string | null`, possible values: `"spending_cap", "payment_failed", "manual"` — Reason for account freeze

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "tier": "free",
    "spending_cap_cents": 1,
    "current_period_usage": 1,
    "frozen": true,
    "frozen_reason": "spending_cap"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Update customer settings

- **Method:** `PATCH`
- **Path:** `/v1/customer`
- **Tags:** Customer

Updates customer settings. Currently supports spending cap. Requires JWT authentication and growth or enterprise tier. API key authentication is not supported.

#### Request Body

##### Content-Type: application/json

- **`spending_cap_cents` (required)**

  `integer`, format: `int64` — New spending cap in cents (0 = no cap)

**Example:**

```json
{
  "spending_cap_cents": 0
}
```

#### Responses

##### Status: 200 Updated customer profile

###### Content-Type: application/json

- **`data`**

  `object`

  - **`current_period_usage` (required)**

    `integer`, format: `int64` — Usage count in current billing period

  - **`frozen` (required)**

    `boolean` — Whether the account is frozen

  - **`spending_cap_cents` (required)**

    `integer`, format: `int64` — Spending cap in cents (0 = no cap)

  - **`tier` (required)**

    `string`, possible values: `"free", "growth", "enterprise"` — Customer's billing tier

  - **`frozen_reason`**

    `string | null`, possible values: `"spending_cap", "payment_failed", "manual"` — Reason for account freeze

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "tier": "free",
    "spending_cap_cents": 1,
    "current_period_usage": 1,
    "frozen": true,
    "frozen_reason": "spending_cap"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Create Stripe checkout session

- **Method:** `POST`
- **Path:** `/v1/billing/checkout`
- **Tags:** Billing

Creates a Stripe Checkout session for plan upgrade. Returns a URL to redirect the user to Stripe's hosted checkout page. Requires JWT authentication (dashboard endpoint). API key authentication is not supported.

The `plan` field selects which paid plan to check out into. Only purchasable plans are permitted (currently: `growth`); any other value is rejected with 400.

#### Request Body

##### Content-Type: application/json

- **`plan` (required)**

  `string`, possible values: `"growth"` — Paid plan to check out into. Only purchasable plans are permitted (currently: \`growth\`); anything else is rejected as an invalid enum value.

**Example:**

```json
{
  "plan": "growth"
}
```

#### Responses

##### Status: 200 Checkout session created

###### Content-Type: application/json

- **`data`**

  `object`

  - **`url` (required)**

    `string`, format: `uri` — Stripe Checkout URL to redirect user to

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "url": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 503 Upstream service temporarily unavailable (e.g. Stripe rate limit)

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Create Stripe billing portal session

- **Method:** `POST`
- **Path:** `/v1/billing/portal`
- **Tags:** Billing

Creates a Stripe Billing Portal session for managing subscriptions, payment methods, and invoices. Returns a URL to redirect the user. Requires JWT authentication (dashboard endpoint). API key authentication is not supported.

#### Responses

##### Status: 200 Portal session created

###### Content-Type: application/json

- **`data`**

  `object`

  - **`url` (required)**

    `string`, format: `uri` — Stripe Billing Portal URL to redirect user to

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "url": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 503 Upstream service temporarily unavailable (e.g. Stripe rate limit)

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List invoices

- **Method:** `GET`
- **Path:** `/v1/billing/invoices`
- **Tags:** Billing

Returns the authenticated user's Stripe invoices. Requires JWT authentication (dashboard endpoint). API key authentication is not supported.

#### Responses

##### Status: 200 Invoice list

###### Content-Type: application/json

- **`data`**

  `object`

  - **`invoices` (required)**

    `array`

    **Items:**

    - **`amount_cents` (required)**

      `integer`, format: `int64` — Invoice amount in cents

    - **`created_at` (required)**

      `string`, format: `date-time` — When the invoice was created

    - **`id` (required)**

      `string` — Stripe invoice ID

    - **`status` (required)**

      `string`, possible values: `"paid", "open", "void", "draft"` — Invoice status

    - **`pdf_url`**

      `string | null`, format: `uri` — URL to download the invoice PDF

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": {
    "invoices": [
      {
        "id": "",
        "amount_cents": 1,
        "status": "paid",
        "created_at": "",
        "pdf_url": null
      }
    ]
  },
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 503 Upstream service temporarily unavailable (e.g. Stripe rate limit)

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Register a custom domain

- **Method:** `POST`
- **Path:** `/v1/domains`
- **Tags:** Domains

Registers a new custom domain for the authenticated user. Returns DNS records that must be configured before verification. Requires enterprise plan.

#### Request Body

##### Content-Type: application/json

- **`domain` (required)**

  `string` — The custom domain name to register

**Example:**

```json
{
  "domain": "secure.example.com"
}
```

#### Responses

##### Status: 201 Domain registered successfully

###### Content-Type: application/json

- **`data`**

  `object`

  - **`acme_cname_target`**

    `string` — CNAME target for ACME certificate provisioning

  - **`activated_at`**

    `string | null`, format: `date-time`

  - **`created_at`**

    `string`, format: `date-time`

  - **`dns_records`**

    `array` — Required DNS records for domain setup

    **Items:**

    - **`name`**

      `string` — DNS record name

    - **`type`**

      `string` — DNS record type (TXT, CNAME)

    - **`value`**

      `string` — DNS record value

    - **`verified`**

      `boolean` — Whether this record has been verified

  - **`domain`**

    `string` — The custom domain name

  - **`ready_for_qurls`**

    `boolean` — Whether this domain is ready to be used with qURLs (status is verified, provisioning\_tls, or active)

  - **`status`**

    `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"` — Current domain status

  - **`token_expires_at`**

    `string | null`, format: `date-time` — When the verification token expires. Omitted for domains created before token expiry was introduced (those tokens never expire). After expiry, use the regenerate-token endpoint to get a new one.

  - **`verification_token`**

    `string` — TXT record value for DNS verification

  - **`verified_at`**

    `string | null`, format: `date-time`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "domain": "",
    "status": "pending_verification",
    "verification_token": "",
    "token_expires_at": null,
    "acme_cname_target": "",
    "created_at": "",
    "verified_at": null,
    "activated_at": null,
    "ready_for_qurls": true,
    "dns_records": [
      {
        "type": "",
        "name": "",
        "value": "",
        "verified": true
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 409 Domain already registered

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List domains

- **Method:** `GET`
- **Path:** `/v1/domains`
- **Tags:** Domains

Lists custom domains for the authenticated user.

#### Responses

##### Status: 200 List of domains

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`acme_cname_target`**

    `string` — CNAME target for ACME certificate provisioning

  - **`activated_at`**

    `string | null`, format: `date-time`

  - **`created_at`**

    `string`, format: `date-time`

  - **`dns_records`**

    `array` — Required DNS records for domain setup

    **Items:**

    - **`name`**

      `string` — DNS record name

    - **`type`**

      `string` — DNS record type (TXT, CNAME)

    - **`value`**

      `string` — DNS record value

    - **`verified`**

      `boolean` — Whether this record has been verified

  - **`domain`**

    `string` — The custom domain name

  - **`ready_for_qurls`**

    `boolean` — Whether this domain is ready to be used with qURLs (status is verified, provisioning\_tls, or active)

  - **`status`**

    `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"` — Current domain status

  - **`token_expires_at`**

    `string | null`, format: `date-time` — When the verification token expires. Omitted for domains created before token expiry was introduced (those tokens never expire). After expiry, use the regenerate-token endpoint to get a new one.

  - **`verification_token`**

    `string` — TXT record value for DNS verification

  - **`verified_at`**

    `string | null`, format: `date-time`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "domain": "",
      "status": "pending_verification",
      "verification_token": "",
      "token_expires_at": null,
      "acme_cname_target": "",
      "created_at": "",
      "verified_at": null,
      "activated_at": null,
      "ready_for_qurls": true,
      "dns_records": [
        {
          "type": "",
          "name": "",
          "value": "",
          "verified": true
        }
      ]
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Get domain status

- **Method:** `GET`
- **Path:** `/v1/domains/{domain}`
- **Tags:** Domains

Retrieves the status and DNS configuration for a custom domain.

#### Responses

##### Status: 200 Domain details

###### Content-Type: application/json

- **`data`**

  `object`

  - **`acme_cname_target`**

    `string` — CNAME target for ACME certificate provisioning

  - **`activated_at`**

    `string | null`, format: `date-time`

  - **`created_at`**

    `string`, format: `date-time`

  - **`dns_records`**

    `array` — Required DNS records for domain setup

    **Items:**

    - **`name`**

      `string` — DNS record name

    - **`type`**

      `string` — DNS record type (TXT, CNAME)

    - **`value`**

      `string` — DNS record value

    - **`verified`**

      `boolean` — Whether this record has been verified

  - **`domain`**

    `string` — The custom domain name

  - **`ready_for_qurls`**

    `boolean` — Whether this domain is ready to be used with qURLs (status is verified, provisioning\_tls, or active)

  - **`status`**

    `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"` — Current domain status

  - **`token_expires_at`**

    `string | null`, format: `date-time` — When the verification token expires. Omitted for domains created before token expiry was introduced (those tokens never expire). After expiry, use the regenerate-token endpoint to get a new one.

  - **`verification_token`**

    `string` — TXT record value for DNS verification

  - **`verified_at`**

    `string | null`, format: `date-time`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "domain": "",
    "status": "pending_verification",
    "verification_token": "",
    "token_expires_at": null,
    "acme_cname_target": "",
    "created_at": "",
    "verified_at": null,
    "activated_at": null,
    "ready_for_qurls": true,
    "dns_records": [
      {
        "type": "",
        "name": "",
        "value": "",
        "verified": true
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Remove domain

- **Method:** `DELETE`
- **Path:** `/v1/domains/{domain}`
- **Tags:** Domains

Removes a custom domain registration.

#### Responses

##### Status: 204 Domain removed successfully

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Trigger DNS verification

- **Method:** `POST`
- **Path:** `/v1/domains/{domain}/verify`
- **Tags:** Domains

Triggers DNS verification for a custom domain. Checks TXT record, ACME CNAME, and traffic routing configuration.

#### Responses

##### Status: 200 Verification result

###### Content-Type: application/json

- **`data`**

  `object`

  - **`checks`**

    `object`

    - **`acme_cname`**

      `object`

      - **`verified` (required)**

        `boolean`

      - **`error`**

        `string` — Error message when the check fails (omitted when verified).

      - **`found`**

        `string` — The actual value found in DNS (omitted when nothing was found).

    - **`traffic_routing`**

      `object`

      - **`verified` (required)**

        `boolean`

      - **`error`**

        `string` — Error message when the check fails (omitted when verified).

      - **`found`**

        `string` — The actual value found in DNS (omitted when nothing was found).

    - **`txt`**

      `object`

      - **`verified` (required)**

        `boolean`

      - **`error`**

        `string` — Error message when the check fails (omitted when verified).

      - **`found`**

        `string` — The actual value found in DNS (omitted when nothing was found).

  - **`domain`**

    `string`

  - **`status`**

    `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "domain": "",
    "status": "pending_verification",
    "checks": {
      "txt": {
        "verified": true,
        "error": "",
        "found": ""
      },
      "acme_cname": {
        "verified": true,
        "error": "",
        "found": ""
      },
      "traffic_routing": {
        "verified": true,
        "error": "",
        "found": ""
      }
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 422 Verification token has expired — use POST /v1/domains/{domain}/regenerate-token to get a new one

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Regenerate verification token

- **Method:** `POST`
- **Path:** `/v1/domains/{domain}/regenerate-token`
- **Tags:** Domains

Regenerates the DNS verification token for a custom domain. The previous token is invalidated immediately. A new TXT record must be configured with the returned token before calling the verify endpoint.

Only allowed for domains in `pending_verification` or `failed` status. The new token expires after 7 days.

#### Responses

##### Status: 200 Token regenerated successfully

###### Content-Type: application/json

- **`data`**

  `object`

  - **`acme_cname_target`**

    `string` — CNAME target for ACME certificate provisioning

  - **`activated_at`**

    `string | null`, format: `date-time`

  - **`created_at`**

    `string`, format: `date-time`

  - **`dns_records`**

    `array` — Required DNS records for domain setup

    **Items:**

    - **`name`**

      `string` — DNS record name

    - **`type`**

      `string` — DNS record type (TXT, CNAME)

    - **`value`**

      `string` — DNS record value

    - **`verified`**

      `boolean` — Whether this record has been verified

  - **`domain`**

    `string` — The custom domain name

  - **`ready_for_qurls`**

    `boolean` — Whether this domain is ready to be used with qURLs (status is verified, provisioning\_tls, or active)

  - **`status`**

    `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"` — Current domain status

  - **`token_expires_at`**

    `string | null`, format: `date-time` — When the verification token expires. Omitted for domains created before token expiry was introduced (those tokens never expire). After expiry, use the regenerate-token endpoint to get a new one.

  - **`verification_token`**

    `string` — TXT record value for DNS verification

  - **`verified_at`**

    `string | null`, format: `date-time`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "domain": "",
    "status": "pending_verification",
    "verification_token": "",
    "token_expires_at": null,
    "acme_cname_target": "",
    "created_at": "",
    "verified_at": null,
    "activated_at": null,
    "ready_for_qurls": true,
    "dns_records": [
      {
        "type": "",
        "name": "",
        "value": "",
        "verified": true
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 409 Token regeneration not allowed for current domain status

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List webhooks

- **Method:** `GET`
- **Path:** `/v1/webhooks`
- **Tags:** Webhooks

Lists webhook endpoints owned by the authenticated user.

#### Responses

##### Status: 200 List of webhooks

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`created_at`**

    `string`, format: `date-time`

  - **`description`**

    `string`

  - **`events`**

    `array`

    **Items:**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

  - **`failure_count`**

    `integer` — Consecutive delivery failures

  - **`last_delivery_success`**

    `boolean`

  - **`last_delivery_time`**

    `integer` — Unix timestamp of last delivery attempt

  - **`status`**

    `string`, possible values: `"active", "disabled"`

  - **`updated_at`**

    `string`, format: `date-time`

  - **`url`**

    `string`, format: `uri`

  - **`webhook_id`**

    `string`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "webhook_id": "",
      "url": "",
      "events": [
        "qurl.created"
      ],
      "status": "active",
      "description": "",
      "created_at": "",
      "updated_at": "",
      "failure_count": 1,
      "last_delivery_success": true,
      "last_delivery_time": 1
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Create a webhook

- **Method:** `POST`
- **Path:** `/v1/webhooks`
- **Tags:** Webhooks

Creates a new webhook endpoint. The secret is returned only once in the response. Store it securely - it cannot be retrieved later (only regenerated).

#### Request Body

##### Content-Type: application/json

- **`events` (required)**

  `array` — Event types to subscribe to

  **Items:**

  `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

- **`url` (required)**

  `string`, format: `uri` — HTTPS endpoint URL to receive webhook events (max 2048 characters)

- **`description`**

  `string` — Human-readable description

**Example:**

```json
{
  "url": "https://example.com/webhooks/qurl",
  "events": [
    "qurl.created"
  ],
  "description": ""
}
```

#### Responses

##### Status: 201 Webhook created successfully

###### Content-Type: application/json

- **`data`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "webhook_id": "",
    "url": "",
    "events": [
      "qurl.created"
    ],
    "status": "active",
    "description": "",
    "created_at": "",
    "updated_at": "",
    "failure_count": 1,
    "last_delivery_success": true,
    "last_delivery_time": 1,
    "secret": "whsec_K8xQp9H2sJ9Lx7R4AaBbCcDdEeFf..."
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List available event types

- **Method:** `GET`
- **Path:** `/v1/webhooks/events`
- **Tags:** Webhooks

Returns all available webhook event types with descriptions.

#### Responses

##### Status: 200 List of event types

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`category`**

    `string`, possible values: `"resource", "access", "quota", "token"`

  - **`description`**

    `string`

  - **`type`**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": [
    {
      "type": "qurl.created",
      "category": "resource",
      "description": ""
    }
  ],
  "meta": {
    "request_id": ""
  }
}
```

### Get a webhook

- **Method:** `GET`
- **Path:** `/v1/webhooks/{id}`
- **Tags:** Webhooks

Retrieves a single webhook by ID.

#### Responses

##### Status: 200 Webhook details

###### Content-Type: application/json

- **`data`**

  `object`

  - **`created_at`**

    `string`, format: `date-time`

  - **`description`**

    `string`

  - **`events`**

    `array`

    **Items:**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

  - **`failure_count`**

    `integer` — Consecutive delivery failures

  - **`last_delivery_success`**

    `boolean`

  - **`last_delivery_time`**

    `integer` — Unix timestamp of last delivery attempt

  - **`status`**

    `string`, possible values: `"active", "disabled"`

  - **`updated_at`**

    `string`, format: `date-time`

  - **`url`**

    `string`, format: `uri`

  - **`webhook_id`**

    `string`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "webhook_id": "",
    "url": "",
    "events": [
      "qurl.created"
    ],
    "status": "active",
    "description": "",
    "created_at": "",
    "updated_at": "",
    "failure_count": 1,
    "last_delivery_success": true,
    "last_delivery_time": 1
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Update a webhook

- **Method:** `PATCH`
- **Path:** `/v1/webhooks/{id}`
- **Tags:** Webhooks

Updates a webhook's configuration.

#### Request Body

##### Content-Type: application/json

- **`description`**

  `string`

- **`events`**

  `array`

  **Items:**

  `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

- **`status`**

  `string`, possible values: `"active", "disabled"`

- **`url`**

  `string`, format: `uri`

**Example:**

```json
{
  "url": "",
  "events": [
    "qurl.created"
  ],
  "description": "",
  "status": "active"
}
```

#### Responses

##### Status: 200 Webhook updated successfully

###### Content-Type: application/json

- **`data`**

  `object`

  - **`created_at`**

    `string`, format: `date-time`

  - **`description`**

    `string`

  - **`events`**

    `array`

    **Items:**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

  - **`failure_count`**

    `integer` — Consecutive delivery failures

  - **`last_delivery_success`**

    `boolean`

  - **`last_delivery_time`**

    `integer` — Unix timestamp of last delivery attempt

  - **`status`**

    `string`, possible values: `"active", "disabled"`

  - **`updated_at`**

    `string`, format: `date-time`

  - **`url`**

    `string`, format: `uri`

  - **`webhook_id`**

    `string`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "webhook_id": "",
    "url": "",
    "events": [
      "qurl.created"
    ],
    "status": "active",
    "description": "",
    "created_at": "",
    "updated_at": "",
    "failure_count": 1,
    "last_delivery_success": true,
    "last_delivery_time": 1
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Delete a webhook

- **Method:** `DELETE`
- **Path:** `/v1/webhooks/{id}`
- **Tags:** Webhooks

Deletes a webhook endpoint.

#### Responses

##### Status: 204 Webhook deleted successfully

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Regenerate webhook secret

- **Method:** `POST`
- **Path:** `/v1/webhooks/{id}/secret`
- **Tags:** Webhooks

Regenerates the signing secret for a webhook. The old secret is immediately invalidated. The new secret is returned only once - store it securely.

#### Responses

##### Status: 200 Secret regenerated successfully

###### Content-Type: application/json

- **`data`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "webhook_id": "",
    "url": "",
    "events": [
      "qurl.created"
    ],
    "status": "active",
    "description": "",
    "created_at": "",
    "updated_at": "",
    "failure_count": 1,
    "last_delivery_success": true,
    "last_delivery_time": 1,
    "secret": "whsec_K8xQp9H2sJ9Lx7R4AaBbCcDdEeFf..."
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List webhook deliveries

- **Method:** `GET`
- **Path:** `/v1/webhooks/{id}/deliveries`
- **Tags:** Webhooks

Lists recent delivery attempts for a webhook.

#### Responses

##### Status: 200 List of delivery attempts

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`completed_at`**

    `string`, format: `date-time`

  - **`created_at`**

    `string`, format: `date-time`

  - **`delivery_id`**

    `string`

  - **`duration_ms`**

    `integer`

  - **`error_message`**

    `string`

  - **`event_type`**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

  - **`response_body`**

    `string` — Truncated response body (max 8KB)

  - **`response_code`**

    `integer`

  - **`retry_count`**

    `integer`

  - **`status`**

    `string`, possible values: `"pending", "success", "failed", "retrying", "abandoned"`

  - **`webhook_id`**

    `string`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "delivery_id": "",
      "webhook_id": "",
      "event_type": "qurl.created",
      "status": "pending",
      "response_code": 1,
      "response_body": "",
      "error_message": "",
      "duration_ms": 1,
      "retry_count": 1,
      "created_at": "",
      "completed_at": ""
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Create a new API key

- **Method:** `POST`
- **Path:** `/v1/api-keys`
- **Tags:** API Keys

Creates a new API key for the authenticated user. The plaintext API key is returned only once in the response - store it securely. Requires JWT authentication (API keys cannot create API keys).

#### Request Body

##### Content-Type: application/json

- **`name` (required)**

  `string` — Human-readable name for the API key

- **`scopes` (required)**

  `array` — Permissions granted to this API key

  **Items:**

  `string`, possible values: `"qurl:read", "qurl:write", "qurl:resolve", "qurl:agent"`

**Example:**

```json
{
  "name": "",
  "scopes": [
    "qurl:read"
  ]
}
```

#### Responses

##### Status: 201 API key created successfully.

###### Content-Type: application/json

- **`data`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "key_id": "",
    "key_prefix": "",
    "name": "",
    "scopes": [
      ""
    ],
    "status": "active",
    "created_at": "",
    "updated_at": "",
    "last_used_at": "",
    "api_key": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Forbidden. Either: - API key auth was used (JWT required for key management), or - Plan key limit reached (free: 3, growth: 50). Error code: \`api\_key\_limit\`.

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 409 The supplied \`Idempotency-Key\` was previously used with a different request body. Send the original body or use a fresh key.

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 503 Transient failure while processing the idempotent request. Retry the same request with the same \`Idempotency-Key\`.

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List API keys

- **Method:** `GET`
- **Path:** `/v1/api-keys`
- **Tags:** API Keys

Lists API keys owned by the authenticated user. The plaintext API key is never returned in list responses. Requires JWT authentication (API keys cannot list API keys).

#### Responses

##### Status: 200 List of API keys

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`created_at`**

    `string`, format: `date-time`

  - **`key_id`**

    `string`

  - **`key_prefix`**

    `string` — First 12 characters of the key for identification. Includes the environment prefix (e.g., "lv\_live\_a3x9").

  - **`last_used_at`**

    `string`, format: `date-time`

  - **`name`**

    `string`

  - **`scopes`**

    `array` — Permissions granted to this API key. Returned in alphabetical order for deterministic responses.

    **Items:**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`updated_at`**

    `string`, format: `date-time` — Timestamp of the last update to name or scopes. Omitted if the key has never been updated.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "key_id": "",
      "key_prefix": "",
      "name": "",
      "scopes": [
        ""
      ],
      "status": "active",
      "created_at": "",
      "updated_at": "",
      "last_used_at": ""
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Update an API key

- **Method:** `PATCH`
- **Path:** `/v1/api-keys/{key_id}`
- **Tags:** API Keys

Updates the name or scopes of an existing API key. Requires JWT authentication (API keys cannot update API keys). If neither name nor scopes are provided, the existing key is returned unchanged (no-op).

#### Request Body

##### Content-Type: application/json

- **`name`**

  `string` — Updated name for the API key

- **`scopes`**

  `array` — Updated permissions for the API key. Omit this field to leave scopes unchanged. When provided, replaces all existing scopes (not a merge).

  **Items:**

  `string`, possible values: `"qurl:read", "qurl:write", "qurl:resolve", "qurl:agent"`

**Example:**

```json
{
  "name": "",
  "scopes": [
    "qurl:read"
  ]
}
```

#### Responses

##### Status: 200 API key updated successfully

###### Content-Type: application/json

- **`data`**

  `object`

  - **`created_at`**

    `string`, format: `date-time`

  - **`key_id`**

    `string`

  - **`key_prefix`**

    `string` — First 12 characters of the key for identification. Includes the environment prefix (e.g., "lv\_live\_a3x9").

  - **`last_used_at`**

    `string`, format: `date-time`

  - **`name`**

    `string`

  - **`scopes`**

    `array` — Permissions granted to this API key. Returned in alphabetical order for deterministic responses.

    **Items:**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`updated_at`**

    `string`, format: `date-time` — Timestamp of the last update to name or scopes. Omitted if the key has never been updated.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "key_id": "",
    "key_prefix": "",
    "name": "",
    "scopes": [
      ""
    ],
    "status": "active",
    "created_at": "",
    "updated_at": "",
    "last_used_at": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Revoke an API key

- **Method:** `DELETE`
- **Path:** `/v1/api-keys/{key_id}`
- **Tags:** API Keys

Revokes an API key (soft delete). The key status is set to "revoked" and a TTL is set for automatic cleanup after 30 days. Requires JWT authentication (API keys cannot revoke API keys).

#### Responses

##### Status: 204 API key revoked successfully

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Redeem an access code

- **Method:** `POST`
- **Path:** `/v1/access-codes/redeem`
- **Tags:** Access Codes

Redeems an access code to obtain a time-limited access link to a protected resource. This endpoint is public and does not require authentication.

The response contains a redirect URL. The client should redirect the user to this URL to complete resource access.

Bot protection is applied: submissions with a non-empty honeypot field or elapsed time under 3 seconds receive a fake success response.

#### Request Body

##### Content-Type: application/json

- **`code` (required)**

  `string` — The plaintext access code to redeem

- **`elapsed_ms`**

  `integer` — Milliseconds elapsed since page load (for bot detection)

- **`honeypot`**

  `string`, default: `""` — Honeypot field for bot detection (must be empty)

**Example:**

```json
{
  "code": "ac_k8xqp9h2sj9lx7r4abcdef",
  "honeypot": "",
  "elapsed_ms": 5200
}
```

#### Responses

##### Status: 200 Access code redeemed successfully

###### Content-Type: application/json

- **`data`**

  `object`

  - **`redirect_url`**

    `string`, format: `uri` — URL to redirect the user to for resource access

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "redirect_url": "https://qurl.link/#at_k8xqp9h2sj9lx7r4abcdef"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Create an access code

- **Method:** `POST`
- **Path:** `/v1/access-codes`
- **Tags:** Access Codes

Creates a new access code for a qURL resource. The plaintext code is returned only once in the response - store it securely.

Requires system-tier account. The resource must exist and be owned by the authenticated user.

#### Request Body

##### Content-Type: application/json

- **`resource_id` (required)**

  `string` — ID of the qURL resource this code grants access to

- **`expires_at`**

  `string`, format: `date-time` — Optional expiration time for the access code

- **`max_uses`**

  `integer`, default: `0` — Maximum number of times this code can be redeemed (0 = unlimited)

- **`name`**

  `string` — Human-readable label for the access code

**Example:**

```json
{
  "resource_id": "r_k8xqp9h2sj9",
  "name": "Investor Stats Q1",
  "max_uses": 10,
  "expires_at": ""
}
```

#### Responses

##### Status: 201 Access code created successfully

###### Content-Type: application/json

- **`data`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "access_code_id": "acd_k8xqp9h2sj9",
    "resource_id": "r_k8xqp9h2sj9",
    "name": "Investor Stats Q1",
    "status": "active",
    "max_uses": 10,
    "use_count": 3,
    "created_at": "",
    "expires_at": null,
    "code": "ac_k8xqp9h2sj9lx7r4abcdef"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### List access codes

- **Method:** `GET`
- **Path:** `/v1/access-codes`
- **Tags:** Access Codes

Lists access codes owned by the authenticated user, sorted by creation date (newest first). Requires system-tier account.

#### Responses

##### Status: 200 List of access codes

###### Content-Type: application/json

- **`data`**

  `array`

  **Items:**

  - **`access_code_id`**

    `string` — Unique identifier for the access code

  - **`created_at`**

    `string`, format: `date-time`

  - **`expires_at`**

    `string | null`, format: `date-time`

  - **`max_uses`**

    `integer` — Maximum redemptions allowed (0 = unlimited)

  - **`name`**

    `string` — Human-readable label

  - **`resource_id`**

    `string` — ID of the qURL resource this code grants access to

  - **`status`**

    `string`, possible values: `"active", "revoked"` — Current status of the access code

  - **`use_count`**

    `integer` — Number of times this code has been redeemed

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": [
    {
      "access_code_id": "acd_k8xqp9h2sj9",
      "resource_id": "r_k8xqp9h2sj9",
      "name": "Investor Stats Q1",
      "status": "active",
      "max_uses": 10,
      "use_count": 3,
      "created_at": "",
      "expires_at": null
    }
  ],
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### Revoke an access code

- **Method:** `DELETE`
- **Path:** `/v1/access-codes/{id}`
- **Tags:** Access Codes

Revokes an access code, preventing further redemptions. Requires system-tier account. The code must be owned by the authenticated user.

#### Responses

##### Status: 204 Access code revoked successfully

##### Status: 400 Invalid request parameters

###### Content-Type: application/problem+json

- **`error`**

  `object`

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 401 Missing or invalid authentication

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 403 Insufficient permissions or quota exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 404 Resource not found

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 429 Rate limit exceeded

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

##### Status: 500 Internal server error

###### Content-Type: application/problem+json

- **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

- **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

## Schemas

### CreateQurlRequest

- **Type:**`object`

* **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

* **`custom_domain`**

  `string` — Optional custom domain to assign to the auto-created resource. The domain must be registered, active, and owned by the caller. Only allowed when the target URL creates a new resource — rejected if a resource already exists for this target URL.

* **`expires_in`**

  `string` — Duration until expiration. Supports: hours (24h), days (7d), weeks (1w). Minimum: 1 minute. Maximum varies by plan: free=3 days, growth/enterprise=30 days. Default: 24h.

* **`label`**

  `string` — Human-readable label identifying who this qURL is for

* **`max_sessions`**

  `integer` — Maximum concurrent sessions allowed. 0 = unlimited (default), 1-1000 = hard limit.

* **`one_time_use`**

  `boolean`, default: `false` — Whether this qURL expires after a single use

* **`session_duration`**

  `string` — How long access lasts after someone clicks this qURL. Controls the session/cookie lifetime, separate from the qURL link expiration (expires\_in). Supports: seconds (30s), minutes (5m), hours (1h), days (1d). Minimum: 1 second. Maximum: 24 hours. Default: server default (typically 1 hour).

* **`target_url`**

  `string`, format: `uri` — The URL to protect with qURL (max 2048 characters). Required for standard reverse-proxy qURLs.

* **`type`**

  `string`, default: `"url"` — Resource type. The handler accepts only \`url\` on this public surface; see the ResourceType schema for the full runtime enum.

**Example:**

```json
{
  "type": "url",
  "target_url": "https://internal.example.com/dashboard",
  "expires_in": "7d",
  "one_time_use": false,
  "max_sessions": 0,
  "session_duration": "1h",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  },
  "label": "Alice from Acme",
  "custom_domain": "app.example.com"
}
```

### CreateQurlForResourceRequest

- **Type:**`object`

Request body for `POST /v1/resources/{id}/qurls`. The target resource is identified by the URL path; its type determines which fields are accepted (e.g. `max_sessions` is rejected on a tunnel resource).

All fields are optional. An empty body mints a qURL with the owner's plan defaults.

- **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

- **`expires_in`**

  `string` — Duration until expiration. Supports: hours (24h), days (7d), weeks (1w). Minimum: 1 minute. Maximum varies by plan: free=3 days, growth/enterprise=30 days. Default: 24h.

- **`label`**

  `string` — Human-readable label identifying who this qURL is for.

- **`max_sessions`**

  `integer` — Maximum concurrent sessions allowed (URL resources only). 0 = unlimited (default), 1-1000 = hard limit.

- **`one_time_use`**

  `boolean`, default: `false` — Whether this qURL expires after a single use. URL resources only.

- **`session_duration`**

  `string` — How long access lasts after someone clicks this qURL. Controls the session/cookie lifetime, separate from the qURL link expiration (expires\_in). Supports: seconds (30s), minutes (5m), hours (1h), days (1d). Minimum: 1 second. Maximum: 24 hours. Default: server default (typically 1 hour). URL resources only.

**Example:**

```json
{
  "expires_in": "7d",
  "one_time_use": false,
  "max_sessions": 0,
  "session_duration": "1h",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  },
  "label": "Alice from Acme"
}
```

### ResourceType

- **Type:**`string`

Resource type. `url` is a target-URL proxy (default). `tunnel` is a reverse-tunnel resource for LayerV-provisioned tunnel callers. `transit` is connector-owned (user-uploaded content via integrations). See `ResourceData.type` for the read-side redaction contract that applies on management-API read responses for non-`url` rows.

**Example:**

### CreateResourceRequest

- **Type:**`object`

Request body for `POST /v1/resources`.

The endpoint is idempotent on `(owner_id, target_url)` for URL resources — a second call with the same target URL returns the existing resource (200 semantics, served as 201 for shape consistency). To preserve that contract, the `alias` field behaves narrowly on POST:

- If no resource exists yet: creates a new resource carrying the alias (subject to per-owner uniqueness).
- If a matching resource exists AND the request omits `alias`, OR the request `alias` equals the existing binding: returns the existing resource as-is. Idempotent.
- If a matching resource exists AND has no alias, but the request supplies `alias`: returns 409 `resource_alias_mismatch`. The caller must use `PATCH /v1/resources/{id}` to set an alias on an existing resource — the alias is not claimed (so `alias_in_use` would be wrong); the existing resource is the conflict.
- If a matching resource exists AND the request `alias` disagrees with the existing binding: returns 409 `resource_alias_mismatch`. Silently rebinding on what looks like an idempotent create is the failure mode the upgrade-path 409 was designed to prevent; use PATCH to rebind explicitly.

Cross-type idempotency: the dedup index keys on `(owner_id, target_url)` only — type is NOT part of the key. A caller submitting `type: url` for a target\_url that the same owner had previously created with a different type (e.g. via a connector POST that minted a transit row) MAY get back the pre-existing row regardless of the request's `type` field. Inspect the response's `type` to confirm. The dashboard surfaces strip user-content fields on non-url types — see the dashboard separation block on `ResourceData`.

- **`alias`**

  `string` — Optional human-readable handle for the resource, unique per owner. Format: 3–64 lowercase alphanumeric chars and hyphens, must start with a letter and end alphanumeric. Reserved words (e.g. \`admin\`, \`qurl\`, \`tunnel\`) are rejected with 400 \`alias\_reserved\`. Collisions with another of your resources return 409 \`alias\_in\_use\`. Disagreeing with the alias of an existing resource for the same target URL returns 409 \`resource\_alias\_mismatch\`.

- **`custom_domain`**

  `string` — Optional custom domain to bind to this resource. Must be a domain previously verified via the domains API.

- **`description`**

  `string`

- **`tags`**

  `array`

  **Items:**

  `string`

- **`target_url`**

  `string`, format: `uri` — The URL to protect.

- **`type`**

  `string`, possible values: `"url", "tunnel", "transit"`, default: `"url"` — Resource type. \`url\` is a target-URL proxy (default). \`tunnel\` is a reverse-tunnel resource for LayerV-provisioned tunnel callers. \`transit\` is connector-owned (user-uploaded content via integrations). See \`ResourceData.type\` for the read-side redaction contract that applies on management-API read responses for non-\`url\` rows.

**Example:**

```json
{
  "type": "url",
  "target_url": "",
  "description": "",
  "tags": [
    ""
  ],
  "custom_domain": "",
  "alias": "dev-dashboard"
}
```

### UpdateResourceRequest

- **Type:**`object`

* **`alias`**

  `string | null` — Set, rebind, or clear the resource's alias under RFC 7396 JSON Merge Patch semantics: - omitted → no change. - \`null\` → clear the alias and release the uniqueness sentinel. Equivalent to deleting the alias. - string value → set or rebind. Pattern, length, and reserved-word rules match \`CreateResourceRequest.alias\`. Sending an empty string (\`""\`) is rejected with \`400 alias\_invalid\_format\` to avoid the silent-no-op trap on callers building this body from user input — use \`null\` for the clear directive.

* **`custom_domain`**

  `string`

* **`description`**

  `string`

* **`preserve_host`**

  `boolean` — Whether to preserve the original Host header when proxying to the origin via a custom domain. When true, the custom domain is sent as the Host header. When false (default), the target server's hostname is used. Only meaningful when custom\_domain is set.

* **`tags`**

  `array`

  **Items:**

  `string`

**Example:**

```json
{
  "description": "",
  "tags": [
    ""
  ],
  "custom_domain": "",
  "preserve_host": true,
  "alias": null
}
```

### ResourceData

- **Type:**`object`

* **`alias`**

  `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

* **`created_at`**

  `string`, format: `date-time`

* **`custom_domain`**

  `string | null`

* **`description`**

  `string`

* **`expires_at`**

  `string`, format: `date-time`

* **`preserve_host`**

  `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

* **`qurl_count`**

  `integer` — Number of active qURLs for this resource

* **`resource_id`**

  `string`

* **`status`**

  `string`, possible values: `"active", "revoked"`

* **`tags`**

  `array`

  **Items:**

  `string`

* **`target_url`**

  `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

* **`type`**

  `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

* **`upstream_addr`**

  `string` — Internal use.

**Example:**

```json
{
  "resource_id": "",
  "type": "",
  "target_url": "",
  "upstream_addr": "",
  "status": "active",
  "description": "",
  "tags": [
    ""
  ],
  "custom_domain": null,
  "alias": null,
  "preserve_host": false,
  "qurl_count": 1,
  "created_at": "",
  "expires_at": ""
}
```

### ResourceDetailData

- **Type:**`object`

* **`qurls`**

  `array`

  **Items:**

  - **`access_policy`**

    `object` — Access control policy for the qURL

    - **`ai_agent_policy`**

      `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

      - **`allow_categories`**

        `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`block_all`**

        `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

      - **`deny_categories`**

        `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`geo_allowlist`**

      `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`geo_denylist`**

      `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`ip_allowlist`**

      `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`ip_denylist`**

      `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`user_agent_allow_regex`**

      `string` — Regular expression to allow matching user agents (max 256 characters)

    - **`user_agent_deny_regex`**

      `string` — Regular expression to deny matching user agents (max 256 characters)

  - **`created_at`**

    `string`, format: `date-time`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`label`**

    `string`

  - **`max_sessions`**

    `integer`

  - **`one_time_use`**

    `boolean`

  - **`qurl_id`**

    `string`

  - **`qurl_site`**

    `string`

  - **`session_duration`**

    `integer` — Session lifetime in seconds (0 = server default)

  - **`status`**

    `string`, possible values: `"active", "consumed", "expired", "revoked"`

  - **`use_count`**

    `integer`

* **`resource`**

  `object`

  - **`alias`**

    `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

  - **`created_at`**

    `string`, format: `date-time`

  - **`custom_domain`**

    `string | null`

  - **`description`**

    `string`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`preserve_host`**

    `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

  - **`qurl_count`**

    `integer` — Number of active qURLs for this resource

  - **`resource_id`**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`tags`**

    `array`

    **Items:**

    `string`

  - **`target_url`**

    `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

  - **`type`**

    `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

  - **`upstream_addr`**

    `string` — Internal use.

**Example:**

```json
{
  "resource": {
    "resource_id": "",
    "type": "",
    "target_url": "",
    "upstream_addr": "",
    "status": "active",
    "description": "",
    "tags": [
      ""
    ],
    "custom_domain": null,
    "alias": null,
    "preserve_host": false,
    "qurl_count": 1,
    "created_at": "",
    "expires_at": ""
  },
  "qurls": [
    {
      "qurl_id": "",
      "label": "",
      "status": "active",
      "one_time_use": true,
      "max_sessions": 1,
      "session_duration": 1,
      "use_count": 1,
      "qurl_site": "",
      "created_at": "",
      "expires_at": "",
      "access_policy": {
        "ip_allowlist": [
          "192.168.1.0/24",
          "10.0.0.1"
        ],
        "ip_denylist": [
          ""
        ],
        "geo_allowlist": [
          "US",
          "CA",
          "GB"
        ],
        "geo_denylist": [
          "CN",
          "RU"
        ],
        "user_agent_allow_regex": "^Mozilla.*",
        "user_agent_deny_regex": ".*bot.*",
        "ai_agent_policy": {
          "block_all": false,
          "deny_categories": [
            "gptbot",
            "commoncrawl",
            "bytedance"
          ],
          "allow_categories": [
            "claude",
            "chatgpt",
            "perplexity"
          ]
        }
      }
    }
  ]
}
```

### QurlSummary

- **Type:**`object`

* **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

* **`created_at`**

  `string`, format: `date-time`

* **`expires_at`**

  `string`, format: `date-time`

* **`label`**

  `string`

* **`max_sessions`**

  `integer`

* **`one_time_use`**

  `boolean`

* **`qurl_id`**

  `string`

* **`qurl_site`**

  `string`

* **`session_duration`**

  `integer` — Session lifetime in seconds (0 = server default)

* **`status`**

  `string`, possible values: `"active", "consumed", "expired", "revoked"`

* **`use_count`**

  `integer`

**Example:**

```json
{
  "qurl_id": "",
  "label": "",
  "status": "active",
  "one_time_use": true,
  "max_sessions": 1,
  "session_duration": 1,
  "use_count": 1,
  "qurl_site": "",
  "created_at": "",
  "expires_at": "",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  }
}
```

### UpdateQurlTokenRequest

- **Type:**`object`

Update a specific qURL token. Can extend expiration, update label, access policy, or max\_sessions. At least one field must be provided. For expiry: provide exactly one of extend\_by (relative) or expires\_at (absolute).

- **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

- **`expires_at`**

  `string`, format: `date-time` — Absolute expiration timestamp (RFC 3339). Must be in the future and within 30 days. Mutually exclusive with extend\_by.

- **`extend_by`**

  `string` — Duration to extend by (e.g., "24h", "7d"). Minimum: 1 minute. Maximum: 30 days. Mutually exclusive with expires\_at.

- **`label`**

  `string` — Human-readable label for this qURL.

- **`max_sessions`**

  `integer` — Maximum concurrent sessions (0 = unlimited).

- **`session_duration`**

  `string` — How long access lasts after clicking. Minimum: 1 second. Maximum: 24 hours.

**Example:**

```json
{
  "extend_by": "7d",
  "expires_at": "",
  "label": "",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  },
  "max_sessions": 0,
  "session_duration": "1h"
}
```

### QurlSummaryResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`access_policy`**

    `object` — Access control policy for the qURL

    - **`ai_agent_policy`**

      `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

      - **`allow_categories`**

        `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`block_all`**

        `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

      - **`deny_categories`**

        `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`geo_allowlist`**

      `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`geo_denylist`**

      `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`ip_allowlist`**

      `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`ip_denylist`**

      `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`user_agent_allow_regex`**

      `string` — Regular expression to allow matching user agents (max 256 characters)

    - **`user_agent_deny_regex`**

      `string` — Regular expression to deny matching user agents (max 256 characters)

  - **`created_at`**

    `string`, format: `date-time`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`label`**

    `string`

  - **`max_sessions`**

    `integer`

  - **`one_time_use`**

    `boolean`

  - **`qurl_id`**

    `string`

  - **`qurl_site`**

    `string`

  - **`session_duration`**

    `integer` — Session lifetime in seconds (0 = server default)

  - **`status`**

    `string`, possible values: `"active", "consumed", "expired", "revoked"`

  - **`use_count`**

    `integer`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "qurl_id": "",
    "label": "",
    "status": "active",
    "one_time_use": true,
    "max_sessions": 1,
    "session_duration": 1,
    "use_count": 1,
    "qurl_site": "",
    "created_at": "",
    "expires_at": "",
    "access_policy": {
      "ip_allowlist": [
        "192.168.1.0/24",
        "10.0.0.1"
      ],
      "ip_denylist": [
        ""
      ],
      "geo_allowlist": [
        "US",
        "CA",
        "GB"
      ],
      "geo_denylist": [
        "CN",
        "RU"
      ],
      "user_agent_allow_regex": "^Mozilla.*",
      "user_agent_deny_regex": ".*bot.*",
      "ai_agent_policy": {
        "block_all": false,
        "deny_categories": [
          "gptbot",
          "commoncrawl",
          "bytedance"
        ],
        "allow_categories": [
          "claude",
          "chatgpt",
          "perplexity"
        ]
      }
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

### SessionData

- **Type:**`object`

* **`created_at`**

  `string`, format: `date-time`

* **`last_seen_at`**

  `string`, format: `date-time`

* **`qurl_id`**

  `string` — qURL display ID (q\_ prefix) that created this session

* **`session_id`**

  `string` — Unique session identifier

* **`src_ip`**

  `string` — Client IP that created the session

* **`user_agent`**

  `string` — Client user agent

**Example:**

```json
{
  "session_id": "",
  "qurl_id": "",
  "src_ip": "",
  "user_agent": "",
  "created_at": "",
  "last_seen_at": ""
}
```

### SessionListResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`created_at`**

    `string`, format: `date-time`

  - **`last_seen_at`**

    `string`, format: `date-time`

  - **`qurl_id`**

    `string` — qURL display ID (q\_ prefix) that created this session

  - **`session_id`**

    `string` — Unique session identifier

  - **`src_ip`**

    `string` — Client IP that created the session

  - **`user_agent`**

    `string` — Client user agent

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": [
    {
      "session_id": "",
      "qurl_id": "",
      "src_ip": "",
      "user_agent": "",
      "created_at": "",
      "last_seen_at": ""
    }
  ],
  "meta": {
    "request_id": ""
  }
}
```

### SessionTerminateResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`terminated`**

    `integer` — Number of sessions terminated

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "terminated": 1
  },
  "meta": {
    "request_id": ""
  }
}
```

### ResourceResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`alias`**

    `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

  - **`created_at`**

    `string`, format: `date-time`

  - **`custom_domain`**

    `string | null`

  - **`description`**

    `string`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`preserve_host`**

    `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

  - **`qurl_count`**

    `integer` — Number of active qURLs for this resource

  - **`resource_id`**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`tags`**

    `array`

    **Items:**

    `string`

  - **`target_url`**

    `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

  - **`type`**

    `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

  - **`upstream_addr`**

    `string` — Internal use.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "resource_id": "",
    "type": "",
    "target_url": "",
    "upstream_addr": "",
    "status": "active",
    "description": "",
    "tags": [
      ""
    ],
    "custom_domain": null,
    "alias": null,
    "preserve_host": false,
    "qurl_count": 1,
    "created_at": "",
    "expires_at": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

### ResourceDetailResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`qurls`**

    `array`

    **Items:**

    - **`access_policy`**

      `object` — Access control policy for the qURL

      - **`ai_agent_policy`**

        `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

        - **`allow_categories`**

          `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

        - **`block_all`**

          `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

        - **`deny_categories`**

          `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`geo_allowlist`**

        `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`geo_denylist`**

        `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`ip_allowlist`**

        `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`ip_denylist`**

        `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`user_agent_allow_regex`**

        `string` — Regular expression to allow matching user agents (max 256 characters)

      - **`user_agent_deny_regex`**

        `string` — Regular expression to deny matching user agents (max 256 characters)

    - **`created_at`**

      `string`, format: `date-time`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`label`**

      `string`

    - **`max_sessions`**

      `integer`

    - **`one_time_use`**

      `boolean`

    - **`qurl_id`**

      `string`

    - **`qurl_site`**

      `string`

    - **`session_duration`**

      `integer` — Session lifetime in seconds (0 = server default)

    - **`status`**

      `string`, possible values: `"active", "consumed", "expired", "revoked"`

    - **`use_count`**

      `integer`

  - **`resource`**

    `object`

    - **`alias`**

      `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

    - **`created_at`**

      `string`, format: `date-time`

    - **`custom_domain`**

      `string | null`

    - **`description`**

      `string`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`preserve_host`**

      `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

    - **`qurl_count`**

      `integer` — Number of active qURLs for this resource

    - **`resource_id`**

      `string`

    - **`status`**

      `string`, possible values: `"active", "revoked"`

    - **`tags`**

      `array`

      **Items:**

      `string`

    - **`target_url`**

      `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

    - **`type`**

      `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

    - **`upstream_addr`**

      `string` — Internal use.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "resource": {
      "resource_id": "",
      "type": "",
      "target_url": "",
      "upstream_addr": "",
      "status": "active",
      "description": "",
      "tags": [
        ""
      ],
      "custom_domain": null,
      "alias": null,
      "preserve_host": false,
      "qurl_count": 1,
      "created_at": "",
      "expires_at": ""
    },
    "qurls": [
      {
        "qurl_id": "",
        "label": "",
        "status": "active",
        "one_time_use": true,
        "max_sessions": 1,
        "session_duration": 1,
        "use_count": 1,
        "qurl_site": "",
        "created_at": "",
        "expires_at": "",
        "access_policy": {
          "ip_allowlist": [
            "192.168.1.0/24",
            "10.0.0.1"
          ],
          "ip_denylist": [
            ""
          ],
          "geo_allowlist": [
            "US",
            "CA",
            "GB"
          ],
          "geo_denylist": [
            "CN",
            "RU"
          ],
          "user_agent_allow_regex": "^Mozilla.*",
          "user_agent_deny_regex": ".*bot.*",
          "ai_agent_policy": {
            "block_all": false,
            "deny_categories": [
              "gptbot",
              "commoncrawl",
              "bytedance"
            ],
            "allow_categories": [
              "claude",
              "chatgpt",
              "perplexity"
            ]
          }
        }
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

### ResourceListResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`alias`**

    `string | null` — Per-owner human-readable handle for the resource (e.g. \`dev-dashboard\`). \`null\` when no alias is set. Lookup via \`GET /v1/resources?alias={alias}\` (returns a 0- or 1-item list).

  - **`created_at`**

    `string`, format: `date-time`

  - **`custom_domain`**

    `string | null`

  - **`description`**

    `string`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`preserve_host`**

    `boolean`, default: `false` — Whether to preserve the original Host header when proxying via custom domain

  - **`qurl_count`**

    `integer` — Number of active qURLs for this resource

  - **`resource_id`**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`tags`**

    `array`

    **Items:**

    `string`

  - **`target_url`**

    `string` — The URL to protect. Optional — omitted (field absent from the JSON object, NOT serialized as null) when not applicable to this resource, or when the resource is owned by a connector.

  - **`type`**

    `string` — Resource type (see \`ResourceType\` for the full enum). Management-API read responses on connector-owned rows redact user-content fields (\`target\_url\`, \`description\`, \`tags\`, \`custom\_domain\`, \`alias\`, \`preserve\_host\`, \`qurl\_count\`) — list/detail responses include the row but with those fields omitted.

  - **`upstream_addr`**

    `string` — Internal use.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "resource_id": "",
      "type": "",
      "target_url": "",
      "upstream_addr": "",
      "status": "active",
      "description": "",
      "tags": [
        ""
      ],
      "custom_domain": null,
      "alias": null,
      "preserve_host": false,
      "qurl_count": 1,
      "created_at": "",
      "expires_at": ""
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

### UpdateQurlRequest

- **Type:**`object`

Update a qURL resource. Can extend expiration (via extend\_by or expires\_at) and/or update tags and description. At least one field must be provided.

- **`description`**

  `string` — Replace the resource description. Pass an empty string to clear.

- **`expires_at`**

  `string`, format: `date-time` — Absolute expiration time. Mutually exclusive with extend\_by. Must be in the future and within 30 days from now.

- **`extend_by`**

  `string` — Duration to extend by (e.g., "24h", "7d", "1w"). Minimum: 1 minute. Maximum: 30 days. Mutually exclusive with expires\_at.

- **`tags`**

  `array` — Replace all tags on this resource. Tags are lowercased and trimmed server-side. Duplicates are silently removed. Pass an empty array to clear all tags.

  **Items:**

  `string`

**Example:**

```json
{
  "extend_by": "7d",
  "expires_at": "",
  "tags": [
    ""
  ],
  "description": ""
}
```

### MintLinkRequest

- **Type:**`object`

Optionally provide expires\_in (relative) or expires\_at (absolute) — not both. If neither is specified, defaults to 24 hours from now.

- **`access_policy`**

  `object` — Access control policy for the qURL

  - **`ai_agent_policy`**

    `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

    - **`allow_categories`**

      `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`block_all`**

      `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

    - **`deny_categories`**

      `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

      **Items:**

      `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`geo_allowlist`**

    `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`geo_denylist`**

    `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

    **Items:**

    `string`

  - **`ip_allowlist`**

    `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`ip_denylist`**

    `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

    **Items:**

    `string`

  - **`user_agent_allow_regex`**

    `string` — Regular expression to allow matching user agents (max 256 characters)

  - **`user_agent_deny_regex`**

    `string` — Regular expression to deny matching user agents (max 256 characters)

- **`expires_at`**

  `string`, format: `date-time` — Absolute expiration timestamp. Must be in the future and within 30 days from now. Mutually exclusive with expires\_in.

- **`expires_in`**

  `string` — Duration until expiration. Supports: minutes (5m), hours (24h), days (7d), weeks (1w). Minimum: 1 minute. Maximum: 30 days.

- **`label`**

  `string` — Human-readable label identifying who this qURL is for

- **`max_sessions`**

  `integer`, default: `0` — Maximum concurrent sessions (0 = unlimited)

- **`one_time_use`**

  `boolean`, default: `false` — Whether this qURL can only be used once

- **`session_duration`**

  `string` — How long access lasts after clicking. Minimum: 1 second. Maximum: 24 hours.

**Example:**

```json
{
  "expires_in": "5m",
  "expires_at": "",
  "label": "Alice from Acme",
  "one_time_use": false,
  "max_sessions": 0,
  "session_duration": "1h",
  "access_policy": {
    "ip_allowlist": [
      "192.168.1.0/24",
      "10.0.0.1"
    ],
    "ip_denylist": [
      ""
    ],
    "geo_allowlist": [
      "US",
      "CA",
      "GB"
    ],
    "geo_denylist": [
      "CN",
      "RU"
    ],
    "user_agent_allow_regex": "^Mozilla.*",
    "user_agent_deny_regex": ".*bot.*",
    "ai_agent_policy": {
      "block_all": false,
      "deny_categories": [
        "gptbot",
        "commoncrawl",
        "bytedance"
      ],
      "allow_categories": [
        "claude",
        "chatgpt",
        "perplexity"
      ]
    }
  }
}
```

### AccessPolicy

- **Type:**`object`

Access control policy for the qURL

- **`ai_agent_policy`**

  `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

  - **`allow_categories`**

    `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

    **Items:**

    `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

  - **`block_all`**

    `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

  - **`deny_categories`**

    `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

    **Items:**

    `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

- **`geo_allowlist`**

  `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

  **Items:**

  `string`

- **`geo_denylist`**

  `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

  **Items:**

  `string`

- **`ip_allowlist`**

  `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

  **Items:**

  `string`

- **`ip_denylist`**

  `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

  **Items:**

  `string`

- **`user_agent_allow_regex`**

  `string` — Regular expression to allow matching user agents (max 256 characters)

- **`user_agent_deny_regex`**

  `string` — Regular expression to deny matching user agents (max 256 characters)

**Example:**

```json
{
  "ip_allowlist": [
    "192.168.1.0/24",
    "10.0.0.1"
  ],
  "ip_denylist": [
    ""
  ],
  "geo_allowlist": [
    "US",
    "CA",
    "GB"
  ],
  "geo_denylist": [
    "CN",
    "RU"
  ],
  "user_agent_allow_regex": "^Mozilla.*",
  "user_agent_deny_regex": ".*bot.*",
  "ai_agent_policy": {
    "block_all": false,
    "deny_categories": [
      "gptbot",
      "commoncrawl",
      "bytedance"
    ],
    "allow_categories": [
      "claude",
      "chatgpt",
      "perplexity"
    ]
  }
}
```

### AIAgentCategory

- **Type:**`string`

A well-known AI agent category identifier

**Example:**

### AIAgentPolicy

- **Type:**`object`

Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category.

**Important:** This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity.

Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex.

Common examples:

Block all AI agents: { "block\_all": true }

Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] }

Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] }

Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] }

Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

- **`allow_categories`**

  `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

  **Items:**

  `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

- **`block_all`**

  `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

- **`deny_categories`**

  `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

  **Items:**

  `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

**Example:**

```json
{
  "block_all": false,
  "deny_categories": [
    "gptbot",
    "commoncrawl",
    "bytedance"
  ],
  "allow_categories": [
    "claude",
    "chatgpt",
    "perplexity"
  ]
}
```

### QurlData

- **Type:**`object`

Resource container for a protected URL. The `qurls` array (present on single-get responses) contains per-qURL details including access policy and session limits. On list responses, only `qurl_count` is populated.

- **`created_at`**

  `string`, format: `date-time` — When the qURL was created

- **`custom_domain`**

  `string | null` — Custom domain associated with this qURL

- **`description`**

  `string` — Human-readable description

- **`expires_at`**

  `string`, format: `date-time` — When the qURL expires

- **`qurl_count`**

  `integer` — Number of active qURLs (access tokens) for this resource

- **`qurl_site`**

  `string`, format: `uri` — The qURL site URL for accessing this resource

- **`qurls`**

  `array` — Per-qURL details. Present on single-get, omitted on list (too expensive).

  **Items:**

  - **`access_policy`**

    `object` — Access control policy for the qURL

    - **`ai_agent_policy`**

      `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

      - **`allow_categories`**

        `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`block_all`**

        `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

      - **`deny_categories`**

        `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`geo_allowlist`**

      `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`geo_denylist`**

      `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`ip_allowlist`**

      `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`ip_denylist`**

      `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`user_agent_allow_regex`**

      `string` — Regular expression to allow matching user agents (max 256 characters)

    - **`user_agent_deny_regex`**

      `string` — Regular expression to deny matching user agents (max 256 characters)

  - **`created_at`**

    `string`, format: `date-time`

  - **`expires_at`**

    `string`, format: `date-time`

  - **`label`**

    `string`

  - **`max_sessions`**

    `integer`

  - **`one_time_use`**

    `boolean`

  - **`qurl_id`**

    `string`

  - **`qurl_site`**

    `string`

  - **`session_duration`**

    `integer` — Session lifetime in seconds (0 = server default)

  - **`status`**

    `string`, possible values: `"active", "consumed", "expired", "revoked"`

  - **`use_count`**

    `integer`

- **`resource_id`**

  `string` — Unique resource identifier (generated by server)

- **`status`**

  `string`, possible values: `"active", "revoked"` — Current lifecycle status. Computed at response time — resources past their expires\_at are reported as "expired" even if not explicitly revoked.

- **`tags`**

  `array` — Tags for categorization and filtering

  **Items:**

  `string`

- **`target_url`**

  `string`, format: `uri` — The protected backend URL. Optional — omitted (field absent from the JSON object, NOT serialized as null) when the resource is owned by a connector.

**Example:**

```json
{
  "resource_id": "",
  "target_url": "",
  "status": "active",
  "created_at": "",
  "expires_at": "",
  "description": "",
  "tags": [
    ""
  ],
  "qurl_site": "",
  "custom_domain": null,
  "qurl_count": 1,
  "qurls": [
    {
      "qurl_id": "",
      "label": "",
      "status": "active",
      "one_time_use": true,
      "max_sessions": 1,
      "session_duration": 1,
      "use_count": 1,
      "qurl_site": "",
      "created_at": "",
      "expires_at": "",
      "...": "[Additional Properties Truncated]"
    }
  ]
}
```

### CreateQurlData

- **Type:**`object`

Response data for qURL creation

- **`branded_domain`**

  `string` — Customer's bare branded hostname for anchor-text display alongside \`qurl\_link\`. See \`MintLinkData.branded\_domain\` for the canonical description (rendering pattern, \`provisioning\_tls\` caveat, live-status gating). Populated when the resource has a \`custom\_domain\` whose status is usable; omitted otherwise. Note: requests replayed via \`Idempotency-Key\` return the cached response, so \`branded\_domain\` reflects the domain status at the time of the original request — not the current status. Consumers caching create responses should re-fetch via the resource if they need a live usability signal.

- **`expires_at`**

  `string`, format: `date-time`

- **`label`**

  `string` — Human-readable label for this qURL

- **`qurl_id`**

  `string` — Unique qURL identifier (q\_ + first 11 hex chars of token hash). Used as NHP resource ID and qurl\_site subdomain. Each qURL gets its own firewall rules keyed by this ID.

- **`qurl_link`**

  `string`, format: `uri` — Ephemeral access link (shown once, cannot be recovered)

- **`qurl_site`**

  `string`, format: `uri` — Per-qURL site URL (https\://{qurl\_id}.qurl.site)

- **`resource_id`**

  `string` — Parent resource ID (auto-created grouping by target URL)

- **`type`**

  `string` — Resource type — echoes the type from the create request.

- **`upstream_addr`**

  `string` — Internal use.

**Example:**

```json
{
  "qurl_id": "q_3a7f2c8e91b",
  "resource_id": "",
  "qurl_link": "",
  "branded_domain": "",
  "qurl_site": "",
  "expires_at": "",
  "label": "",
  "type": "",
  "upstream_addr": ""
}
```

### QurlResponse

- **Type:**`object`

* **`data`**

  `object` — Resource container for a protected URL. The \`qurls\` array (present on single-get responses) contains per-qURL details including access policy and session limits. On list responses, only \`qurl\_count\` is populated.

  - **`created_at`**

    `string`, format: `date-time` — When the qURL was created

  - **`custom_domain`**

    `string | null` — Custom domain associated with this qURL

  - **`description`**

    `string` — Human-readable description

  - **`expires_at`**

    `string`, format: `date-time` — When the qURL expires

  - **`qurl_count`**

    `integer` — Number of active qURLs (access tokens) for this resource

  - **`qurl_site`**

    `string`, format: `uri` — The qURL site URL for accessing this resource

  - **`qurls`**

    `array` — Per-qURL details. Present on single-get, omitted on list (too expensive).

    **Items:**

    - **`access_policy`**

      `object` — Access control policy for the qURL

      - **`ai_agent_policy`**

        `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

        - **`allow_categories`**

          `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

        - **`block_all`**

          `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

        - **`deny_categories`**

          `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`geo_allowlist`**

        `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`geo_denylist`**

        `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`ip_allowlist`**

        `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`ip_denylist`**

        `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`user_agent_allow_regex`**

        `string` — Regular expression to allow matching user agents (max 256 characters)

      - **`user_agent_deny_regex`**

        `string` — Regular expression to deny matching user agents (max 256 characters)

    - **`created_at`**

      `string`, format: `date-time`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`label`**

      `string`

    - **`max_sessions`**

      `integer`

    - **`one_time_use`**

      `boolean`

    - **`qurl_id`**

      `string`

    - **`qurl_site`**

      `string`

    - **`session_duration`**

      `integer` — Session lifetime in seconds (0 = server default)

    - **`status`**

      `string`, possible values: `"active", "consumed", "expired", "revoked"`

    - **`use_count`**

      `integer`

  - **`resource_id`**

    `string` — Unique resource identifier (generated by server)

  - **`status`**

    `string`, possible values: `"active", "revoked"` — Current lifecycle status. Computed at response time — resources past their expires\_at are reported as "expired" even if not explicitly revoked.

  - **`tags`**

    `array` — Tags for categorization and filtering

    **Items:**

    `string`

  - **`target_url`**

    `string`, format: `uri` — The protected backend URL. Optional — omitted (field absent from the JSON object, NOT serialized as null) when the resource is owned by a connector.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "resource_id": "",
    "target_url": "",
    "status": "active",
    "created_at": "",
    "expires_at": "",
    "description": "",
    "tags": [
      ""
    ],
    "qurl_site": "",
    "custom_domain": null,
    "qurl_count": 1,
    "qurls": [
      {
        "qurl_id": "",
        "label": "",
        "status": "active",
        "one_time_use": true,
        "max_sessions": 1,
        "session_duration": 1,
        "use_count": 1,
        "qurl_site": "",
        "created_at": "",
        "expires_at": "",
        "...": "[Additional Properties Truncated]"
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

### CreateQurlResponse

- **Type:**`object`

* **`data`**

  `object` — Response data for qURL creation

  - **`branded_domain`**

    `string` — Customer's bare branded hostname for anchor-text display alongside \`qurl\_link\`. See \`MintLinkData.branded\_domain\` for the canonical description (rendering pattern, \`provisioning\_tls\` caveat, live-status gating). Populated when the resource has a \`custom\_domain\` whose status is usable; omitted otherwise. Note: requests replayed via \`Idempotency-Key\` return the cached response, so \`branded\_domain\` reflects the domain status at the time of the original request — not the current status. Consumers caching create responses should re-fetch via the resource if they need a live usability signal.

  - **`expires_at`**

    `string`, format: `date-time`

  - **`label`**

    `string` — Human-readable label for this qURL

  - **`qurl_id`**

    `string` — Unique qURL identifier (q\_ + first 11 hex chars of token hash). Used as NHP resource ID and qurl\_site subdomain. Each qURL gets its own firewall rules keyed by this ID.

  - **`qurl_link`**

    `string`, format: `uri` — Ephemeral access link (shown once, cannot be recovered)

  - **`qurl_site`**

    `string`, format: `uri` — Per-qURL site URL (https\://{qurl\_id}.qurl.site)

  - **`resource_id`**

    `string` — Parent resource ID (auto-created grouping by target URL)

  - **`type`**

    `string` — Resource type — echoes the type from the create request.

  - **`upstream_addr`**

    `string` — Internal use.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "qurl_id": "q_3a7f2c8e91b",
    "resource_id": "",
    "qurl_link": "",
    "branded_domain": "",
    "qurl_site": "",
    "expires_at": "",
    "label": "",
    "type": "",
    "upstream_addr": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

### ResolveQurlRequest

- **Type:**`object`

* **`access_token` (required)**

  `string` — qURL access token (e.g., "at\_k8xqp9h2sj9lx7r4abcdef")

**Example:**

```json
{
  "access_token": "at_k8xqp9h2sj9lx7r4abcdef"
}
```

### ResolveQurlResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`access_grant`**

    `object` — Details of the firewall access that was granted

    - **`expires_in`**

      `integer` — Seconds until firewall access expires

    - **`granted_at`**

      `string`, format: `date-time` — When the access was granted

    - **`src_ip`**

      `string` — The IP address that was granted access

  - **`resource_id`**

    `string` — qURL resource identifier

  - **`target_url`**

    `string`, format: `uri` — The URL that is now accessible from the caller's IP

* **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "target_url": "https://api.example.com/data",
    "resource_id": "r_k8xqp9h2sj9",
    "access_grant": {
      "expires_in": 305,
      "granted_at": "2026-03-09T15:30:00Z",
      "src_ip": "203.0.113.42"
    }
  },
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### ResolveQurlData

- **Type:**`object`

* **`access_grant`**

  `object` — Details of the firewall access that was granted

  - **`expires_in`**

    `integer` — Seconds until firewall access expires

  - **`granted_at`**

    `string`, format: `date-time` — When the access was granted

  - **`src_ip`**

    `string` — The IP address that was granted access

* **`resource_id`**

  `string` — qURL resource identifier

* **`target_url`**

  `string`, format: `uri` — The URL that is now accessible from the caller's IP

**Example:**

```json
{
  "target_url": "https://api.example.com/data",
  "resource_id": "r_k8xqp9h2sj9",
  "access_grant": {
    "expires_in": 305,
    "granted_at": "2026-03-09T15:30:00Z",
    "src_ip": "203.0.113.42"
  }
}
```

### AccessGrant

- **Type:**`object`

Details of the firewall access that was granted

- **`expires_in`**

  `integer` — Seconds until firewall access expires

- **`granted_at`**

  `string`, format: `date-time` — When the access was granted

- **`src_ip`**

  `string` — The IP address that was granted access

**Example:**

```json
{
  "expires_in": 305,
  "granted_at": "2026-03-09T15:30:00Z",
  "src_ip": "203.0.113.42"
}
```

### QurlListResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`created_at`**

    `string`, format: `date-time` — When the qURL was created

  - **`custom_domain`**

    `string | null` — Custom domain associated with this qURL

  - **`description`**

    `string` — Human-readable description

  - **`expires_at`**

    `string`, format: `date-time` — When the qURL expires

  - **`qurl_count`**

    `integer` — Number of active qURLs (access tokens) for this resource

  - **`qurl_site`**

    `string`, format: `uri` — The qURL site URL for accessing this resource

  - **`qurls`**

    `array` — Per-qURL details. Present on single-get, omitted on list (too expensive).

    **Items:**

    - **`access_policy`**

      `object` — Access control policy for the qURL

      - **`ai_agent_policy`**

        `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

        - **`allow_categories`**

          `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

        - **`block_all`**

          `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

        - **`deny_categories`**

          `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

          **Items:**

          `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`geo_allowlist`**

        `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`geo_denylist`**

        `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

        **Items:**

        `string`

      - **`ip_allowlist`**

        `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`ip_denylist`**

        `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

        **Items:**

        `string`

      - **`user_agent_allow_regex`**

        `string` — Regular expression to allow matching user agents (max 256 characters)

      - **`user_agent_deny_regex`**

        `string` — Regular expression to deny matching user agents (max 256 characters)

    - **`created_at`**

      `string`, format: `date-time`

    - **`expires_at`**

      `string`, format: `date-time`

    - **`label`**

      `string`

    - **`max_sessions`**

      `integer`

    - **`one_time_use`**

      `boolean`

    - **`qurl_id`**

      `string`

    - **`qurl_site`**

      `string`

    - **`session_duration`**

      `integer` — Session lifetime in seconds (0 = server default)

    - **`status`**

      `string`, possible values: `"active", "consumed", "expired", "revoked"`

    - **`use_count`**

      `integer`

  - **`resource_id`**

    `string` — Unique resource identifier (generated by server)

  - **`status`**

    `string`, possible values: `"active", "revoked"` — Current lifecycle status. Computed at response time — resources past their expires\_at are reported as "expired" even if not explicitly revoked.

  - **`tags`**

    `array` — Tags for categorization and filtering

    **Items:**

    `string`

  - **`target_url`**

    `string`, format: `uri` — The protected backend URL. Optional — omitted (field absent from the JSON object, NOT serialized as null) when the resource is owned by a connector.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "resource_id": "",
      "target_url": "",
      "status": "active",
      "created_at": "",
      "expires_at": "",
      "description": "",
      "tags": [
        ""
      ],
      "qurl_site": "",
      "custom_domain": null,
      "qurl_count": 1,
      "qurls": [
        {
          "qurl_id": "",
          "label": "",
          "status": "active",
          "one_time_use": true,
          "max_sessions": 1,
          "session_duration": 1,
          "use_count": 1,
          "qurl_site": "",
          "created_at": "",
          "expires_at": "",
          "...": "[Additional Properties Truncated]"
        }
      ]
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

### MintLinkData

- **Type:**`object`

* **`branded_domain`**

  `string` — Customer's bare branded hostname (e.g. \`customer.com\`) for anchor-text display alongside \`qurl\_link\`. Render as \`\<a href="{qurl\_link}">{branded\_domain}\</a>\` — the access token lives in \`qurl\_link\`'s fragment, not here, so the href must stay \`qurl\_link\`. Populated when the resource has a \`custom\_domain\` whose status is usable (\`verified\`, \`provisioning\_tls\`, or \`active\`) and is owned by the requesting tenant; omitted when no custom domain is set, when the assigned domain has transitioned to a non-usable status, or when the requesting tenant doesn't own the live domain row (defends against domain churn — e.g. a domain deleted and re-registered by a different tenant). During \`provisioning\_tls\` the branded host may not yet be serving HTTPS, so treat the value as a display string until the domain reaches \`active\`.

* **`expires_at`**

  `string`, format: `date-time`

* **`qurl_id`**

  `string` — The minted qURL's primary key (prefix \`q\_\`). Same identifier as \`CreateQurlData.qurl\_id\`. Webhook subscribers receive \`qurl.accessed\` events keyed on this ID, so callers that want to correlate access events back to the recipient/context they minted FOR must capture this value at mint time.

* **`qurl_link`**

  `string`, format: `uri` — Single-use access link

* **`type`**

  `string` — Resource type — echoes the type of the underlying resource.

* **`upstream_addr`**

  `string` — Internal use.

**Example:**

```json
{
  "qurl_id": "q_a1b2c3d4e5f",
  "qurl_link": "",
  "branded_domain": "",
  "expires_at": "",
  "type": "",
  "upstream_addr": ""
}
```

### MintLinkResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`branded_domain`**

    `string` — Customer's bare branded hostname (e.g. \`customer.com\`) for anchor-text display alongside \`qurl\_link\`. Render as \`\<a href="{qurl\_link}">{branded\_domain}\</a>\` — the access token lives in \`qurl\_link\`'s fragment, not here, so the href must stay \`qurl\_link\`. Populated when the resource has a \`custom\_domain\` whose status is usable (\`verified\`, \`provisioning\_tls\`, or \`active\`) and is owned by the requesting tenant; omitted when no custom domain is set, when the assigned domain has transitioned to a non-usable status, or when the requesting tenant doesn't own the live domain row (defends against domain churn — e.g. a domain deleted and re-registered by a different tenant). During \`provisioning\_tls\` the branded host may not yet be serving HTTPS, so treat the value as a display string until the domain reaches \`active\`.

  - **`expires_at`**

    `string`, format: `date-time`

  - **`qurl_id`**

    `string` — The minted qURL's primary key (prefix \`q\_\`). Same identifier as \`CreateQurlData.qurl\_id\`. Webhook subscribers receive \`qurl.accessed\` events keyed on this ID, so callers that want to correlate access events back to the recipient/context they minted FOR must capture this value at mint time.

  - **`qurl_link`**

    `string`, format: `uri` — Single-use access link

  - **`type`**

    `string` — Resource type — echoes the type of the underlying resource.

  - **`upstream_addr`**

    `string` — Internal use.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "qurl_id": "q_a1b2c3d4e5f",
    "qurl_link": "",
    "branded_domain": "",
    "expires_at": "",
    "type": "",
    "upstream_addr": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

### BatchCreateRequest

- **Type:**`object`

* **`items` (required)**

  `array` — Array of qURL creation requests (1-100 items)

  **Items:**

  - **`access_policy`**

    `object` — Access control policy for the qURL

    - **`ai_agent_policy`**

      `object` — Structured policy for controlling AI agent access. Users select well-known categories by name instead of crafting regex patterns. The server maintains the user-agent patterns for each category. \*\*Important:\*\* This blocks agents that self-identify via their User-Agent header. It does not prevent agents that spoof or omit their identity. Evaluation order: AI agent policy is evaluated after IP/Geo rules but before generic user\_agent\_deny\_regex / user\_agent\_allow\_regex. A bot allowed by this policy can still be blocked by user\_agent\_deny\_regex. A bot blocked here cannot be rescued by user\_agent\_allow\_regex. Common examples: Block all AI agents: { "block\_all": true } Block specific crawlers (e.g., GPTBot and Common Crawl) while allowing others: { "deny\_categories": \["gptbot", "commoncrawl"] } Allow only Claude and Perplexity, block all other AI agents: { "allow\_categories": \["claude", "perplexity"] } Block ByteDance and Meta crawlers but allow ChatGPT and Claude: { "deny\_categories": \["bytedance", "meta"], "allow\_categories": \["chatgpt", "claude"] } Note: deny always takes precedence over allow. If an agent appears in both lists, it is blocked.

      - **`allow_categories`**

        `array` — AI agent categories to permit. When set, all other AI agents are blocked. Deny takes precedence. Ignored if block\_all is true.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

      - **`block_all`**

        `boolean`, default: `false` — Block all recognized AI agents regardless of category lists

      - **`deny_categories`**

        `array` — AI agent categories to block. Ignored if block\_all is true. Deny takes precedence over allow.

        **Items:**

        `string`, possible values: `"chatgpt", "gptbot", "claude", "gemini", "perplexity", "cohere", "meta", "bytedance", "amazon", "apple", "commoncrawl", "mistral", "generic_ai"` — A well-known AI agent category identifier

    - **`geo_allowlist`**

      `array` — List of allowed country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`geo_denylist`**

      `array` — List of denied country codes (ISO 3166-1 alpha-2, max 50 entries)

      **Items:**

      `string`

    - **`ip_allowlist`**

      `array` — List of allowed IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`ip_denylist`**

      `array` — List of denied IP addresses or CIDR ranges (max 100 entries)

      **Items:**

      `string`

    - **`user_agent_allow_regex`**

      `string` — Regular expression to allow matching user agents (max 256 characters)

    - **`user_agent_deny_regex`**

      `string` — Regular expression to deny matching user agents (max 256 characters)

  - **`custom_domain`**

    `string` — Optional custom domain to assign to the auto-created resource. The domain must be registered, active, and owned by the caller. Only allowed when the target URL creates a new resource — rejected if a resource already exists for this target URL.

  - **`expires_in`**

    `string` — Duration until expiration. Supports: hours (24h), days (7d), weeks (1w). Minimum: 1 minute. Maximum varies by plan: free=3 days, growth/enterprise=30 days. Default: 24h.

  - **`label`**

    `string` — Human-readable label identifying who this qURL is for

  - **`max_sessions`**

    `integer` — Maximum concurrent sessions allowed. 0 = unlimited (default), 1-1000 = hard limit.

  - **`one_time_use`**

    `boolean`, default: `false` — Whether this qURL expires after a single use

  - **`session_duration`**

    `string` — How long access lasts after someone clicks this qURL. Controls the session/cookie lifetime, separate from the qURL link expiration (expires\_in). Supports: seconds (30s), minutes (5m), hours (1h), days (1d). Minimum: 1 second. Maximum: 24 hours. Default: server default (typically 1 hour).

  - **`target_url`**

    `string`, format: `uri` — The URL to protect with qURL (max 2048 characters). Required for standard reverse-proxy qURLs.

  - **`type`**

    `string`, default: `"url"` — Resource type. The handler accepts only \`url\` on this public surface; see the ResourceType schema for the full runtime enum.

**Example:**

```json
{
  "items": [
    {
      "type": "url",
      "target_url": "https://internal.example.com/dashboard",
      "expires_in": "7d",
      "one_time_use": false,
      "max_sessions": 0,
      "session_duration": "1h",
      "access_policy": {
        "ip_allowlist": [
          "192.168.1.0/24",
          "10.0.0.1"
        ],
        "ip_denylist": [
          ""
        ],
        "geo_allowlist": [
          "US",
          "CA",
          "GB"
        ],
        "geo_denylist": [
          "CN",
          "RU"
        ],
        "user_agent_allow_regex": "^Mozilla.*",
        "user_agent_deny_regex": ".*bot.*",
        "ai_agent_policy": {
          "block_all": false,
          "deny_categories": [
            "gptbot",
            "commoncrawl",
            "bytedance"
          ],
          "allow_categories": [
            "claude",
            "chatgpt",
            "perplexity"
          ]
        }
      },
      "label": "Alice from Acme",
      "custom_domain": "app.example.com"
    }
  ]
}
```

### BatchCreateResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`failed`**

    `integer` — Number of failed creations

  - **`results`**

    `array`

    **Items:**

    - **`branded_domain`**

      `string` — Customer's bare branded hostname (if success). See \`MintLinkData.branded\_domain\` for the canonical description, including the \`\<a href="{qurl\_link}">{branded\_domain}\</a>\` rendering pattern, the \`provisioning\_tls\` HTTPS-not-yet-live caveat, and the live-status + tenant-ownership gating.

    - **`error`**

      `object` — Error details (if failed)

      - **`code`**

        `string`

      - **`message`**

        `string`

    - **`expires_at`**

      `string`, format: `date-time` — Expiration time (if success)

    - **`index`**

      `integer` — Original index in the request array

    - **`qurl_link`**

      `string`, format: `uri` — Access link (if success)

    - **`qurl_site`**

      `string`, format: `uri` — qURL site URL (if success)

    - **`resource_id`**

      `string` — Created resource ID (if success)

    - **`success`**

      `boolean`

  - **`succeeded`**

    `integer` — Number of successfully created qURLs

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "succeeded": 1,
    "failed": 1,
    "results": [
      {
        "index": 1,
        "success": true,
        "resource_id": "",
        "qurl_link": "",
        "branded_domain": "",
        "qurl_site": "",
        "expires_at": "",
        "error": {
          "code": "",
          "message": ""
        }
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

### BatchItemResult

- **Type:**`object`

* **`branded_domain`**

  `string` — Customer's bare branded hostname (if success). See \`MintLinkData.branded\_domain\` for the canonical description, including the \`\<a href="{qurl\_link}">{branded\_domain}\</a>\` rendering pattern, the \`provisioning\_tls\` HTTPS-not-yet-live caveat, and the live-status + tenant-ownership gating.

* **`error`**

  `object` — Error details (if failed)

  - **`code`**

    `string`

  - **`message`**

    `string`

* **`expires_at`**

  `string`, format: `date-time` — Expiration time (if success)

* **`index`**

  `integer` — Original index in the request array

* **`qurl_link`**

  `string`, format: `uri` — Access link (if success)

* **`qurl_site`**

  `string`, format: `uri` — qURL site URL (if success)

* **`resource_id`**

  `string` — Created resource ID (if success)

* **`success`**

  `boolean`

**Example:**

```json
{
  "index": 1,
  "success": true,
  "resource_id": "",
  "qurl_link": "",
  "branded_domain": "",
  "qurl_site": "",
  "expires_at": "",
  "error": {
    "code": "",
    "message": ""
  }
}
```

### QuotaData

- **Type:**`object`

* **`period_end`**

  `string`, format: `date-time`

* **`period_start`**

  `string`, format: `date-time`

* **`plan`**

  `string`, possible values: `"free", "growth", "enterprise"`

* **`rate_limits`**

  `object`

  - **`create_per_hour`**

    `integer`

  - **`create_per_minute`**

    `integer`

  - **`list_per_minute`**

    `integer`

  - **`max_active_qurls`**

    `integer` — Maximum active qURLs allowed (-1 = unlimited)

  - **`max_expiry_seconds`**

    `integer` — Maximum expiry duration in seconds (free=259200/3d, growth=2592000/30d, enterprise=2592000/30d)

  - **`max_tokens_per_qurl`**

    `integer` — Maximum tokens per qURL (-1 = unlimited)

  - **`resolve_per_minute`**

    `integer` — Rate limit for token resolution requests

* **`usage`**

  `object`

  - **`active_qurls`**

    `integer` — Currently active qURLs

  - **`active_qurls_percent`**

    `number | null`, format: `float` — Percentage of max\_active\_qurls used (0-100, null if unlimited)

  - **`qurls_created`**

    `integer` — Total qURLs created this period

  - **`total_accesses`**

    `integer` — Total access attempts this period

**Example:**

```json
{
  "plan": "free",
  "period_start": "",
  "period_end": "",
  "rate_limits": {
    "create_per_minute": 1,
    "create_per_hour": 1,
    "list_per_minute": 1,
    "resolve_per_minute": 1,
    "max_active_qurls": 1,
    "max_tokens_per_qurl": 1,
    "max_expiry_seconds": 1
  },
  "usage": {
    "qurls_created": 1,
    "active_qurls": 1,
    "active_qurls_percent": null,
    "total_accesses": 1
  }
}
```

### QuotaResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`period_end`**

    `string`, format: `date-time`

  - **`period_start`**

    `string`, format: `date-time`

  - **`plan`**

    `string`, possible values: `"free", "growth", "enterprise"`

  - **`rate_limits`**

    `object`

    - **`create_per_hour`**

      `integer`

    - **`create_per_minute`**

      `integer`

    - **`list_per_minute`**

      `integer`

    - **`max_active_qurls`**

      `integer` — Maximum active qURLs allowed (-1 = unlimited)

    - **`max_expiry_seconds`**

      `integer` — Maximum expiry duration in seconds (free=259200/3d, growth=2592000/30d, enterprise=2592000/30d)

    - **`max_tokens_per_qurl`**

      `integer` — Maximum tokens per qURL (-1 = unlimited)

    - **`resolve_per_minute`**

      `integer` — Rate limit for token resolution requests

  - **`usage`**

    `object`

    - **`active_qurls`**

      `integer` — Currently active qURLs

    - **`active_qurls_percent`**

      `number | null`, format: `float` — Percentage of max\_active\_qurls used (0-100, null if unlimited)

    - **`qurls_created`**

      `integer` — Total qURLs created this period

    - **`total_accesses`**

      `integer` — Total access attempts this period

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "plan": "free",
    "period_start": "",
    "period_end": "",
    "rate_limits": {
      "create_per_minute": 1,
      "create_per_hour": 1,
      "list_per_minute": 1,
      "resolve_per_minute": 1,
      "max_active_qurls": 1,
      "max_tokens_per_qurl": 1,
      "max_expiry_seconds": 1
    },
    "usage": {
      "qurls_created": 1,
      "active_qurls": 1,
      "active_qurls_percent": null,
      "total_accesses": 1
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

### UsageCostEstimate

- **Type:**`object`

* **`amount_cents` (required)**

  `integer` — Cost in cents

* **`currency` (required)**

  `string` — Currency code (e.g. "usd")

* **`description` (required)**

  `string` — Human-readable breakdown

**Example:**

```json
{
  "currency": "usd",
  "amount_cents": 1500,
  "description": "150 qURLs x $0.10/qURL"
}
```

### UsageCurrentPeriodData

- **Type:**`object`

* **`active_qurls` (required)**

  `integer` — Number of currently active (non-expired, non-revoked) qURLs

* **`period_end` (required)**

  `string`, format: `date-time` — End of the current billing period (UTC)

* **`period_start` (required)**

  `string`, format: `date-time` — Start of the current billing period (UTC)

* **`qurls_created` (required)**

  `integer` — Number of qURLs created in this period

* **`tier` (required)**

  `string`, possible values: `"free", "growth", "enterprise"` — Current billing tier

* **`cost_estimate`**

  `object`

  - **`amount_cents` (required)**

    `integer` — Cost in cents

  - **`currency` (required)**

    `string` — Currency code (e.g. "usd")

  - **`description` (required)**

    `string` — Human-readable breakdown

**Example:**

```json
{
  "tier": "free",
  "period_start": "",
  "period_end": "",
  "qurls_created": 1,
  "active_qurls": 1,
  "cost_estimate": {
    "currency": "usd",
    "amount_cents": 1500,
    "description": "150 qURLs x $0.10/qURL"
  }
}
```

### UsageCurrentPeriodResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`active_qurls` (required)**

    `integer` — Number of currently active (non-expired, non-revoked) qURLs

  - **`period_end` (required)**

    `string`, format: `date-time` — End of the current billing period (UTC)

  - **`period_start` (required)**

    `string`, format: `date-time` — Start of the current billing period (UTC)

  - **`qurls_created` (required)**

    `integer` — Number of qURLs created in this period

  - **`tier` (required)**

    `string`, possible values: `"free", "growth", "enterprise"` — Current billing tier

  - **`cost_estimate`**

    `object`

    - **`amount_cents` (required)**

      `integer` — Cost in cents

    - **`currency` (required)**

      `string` — Currency code (e.g. "usd")

    - **`description` (required)**

      `string` — Human-readable breakdown

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "tier": "free",
    "period_start": "",
    "period_end": "",
    "qurls_created": 1,
    "active_qurls": 1,
    "cost_estimate": {
      "currency": "usd",
      "amount_cents": 1500,
      "description": "150 qURLs x $0.10/qURL"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

### UsageDailyEntry

- **Type:**`object`

* **`date` (required)**

  `string`, format: `date` — Calendar date (YYYY-MM-DD)

* **`qurls_created` (required)**

  `integer` — qURLs created on this date

**Example:**

```json
{
  "date": "",
  "qurls_created": 1
}
```

### UsageDailyData

- **Type:**`object`

* **`daily` (required)**

  `array` — One entry per day from period\_start to today

  **Items:**

  - **`date` (required)**

    `string`, format: `date` — Calendar date (YYYY-MM-DD)

  - **`qurls_created` (required)**

    `integer` — qURLs created on this date

* **`period_end` (required)**

  `string`, format: `date-time` — End of the current billing period (UTC)

* **`period_start` (required)**

  `string`, format: `date-time` — Start of the current billing period (UTC)

* **`tier` (required)**

  `string`, possible values: `"free", "growth", "enterprise"` — Current billing tier

**Example:**

```json
{
  "tier": "free",
  "period_start": "",
  "period_end": "",
  "daily": [
    {
      "date": "",
      "qurls_created": 1
    }
  ]
}
```

### UsageDailyResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`daily` (required)**

    `array` — One entry per day from period\_start to today

    **Items:**

    - **`date` (required)**

      `string`, format: `date` — Calendar date (YYYY-MM-DD)

    - **`qurls_created` (required)**

      `integer` — qURLs created on this date

  - **`period_end` (required)**

    `string`, format: `date-time` — End of the current billing period (UTC)

  - **`period_start` (required)**

    `string`, format: `date-time` — Start of the current billing period (UTC)

  - **`tier` (required)**

    `string`, possible values: `"free", "growth", "enterprise"` — Current billing tier

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "tier": "free",
    "period_start": "",
    "period_end": "",
    "daily": [
      {
        "date": "",
        "qurls_created": 1
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

### CreateApiKeyRequest

- **Type:**`object`

* **`name` (required)**

  `string` — Human-readable name for the API key

* **`scopes` (required)**

  `array` — Permissions granted to this API key

  **Items:**

  `string`, possible values: `"qurl:read", "qurl:write", "qurl:resolve", "qurl:agent"`

**Example:**

```json
{
  "name": "",
  "scopes": [
    "qurl:read"
  ]
}
```

### UpdateApiKeyRequest

- **Type:**`object`

* **`name`**

  `string` — Updated name for the API key

* **`scopes`**

  `array` — Updated permissions for the API key. Omit this field to leave scopes unchanged. When provided, replaces all existing scopes (not a merge).

  **Items:**

  `string`, possible values: `"qurl:read", "qurl:write", "qurl:resolve", "qurl:agent"`

**Example:**

```json
{
  "name": "",
  "scopes": [
    "qurl:read"
  ]
}
```

### ApiKeyData

- **Type:**`object`

* **`created_at`**

  `string`, format: `date-time`

* **`key_id`**

  `string`

* **`key_prefix`**

  `string` — First 12 characters of the key for identification. Includes the environment prefix (e.g., "lv\_live\_a3x9").

* **`last_used_at`**

  `string`, format: `date-time`

* **`name`**

  `string`

* **`scopes`**

  `array` — Permissions granted to this API key. Returned in alphabetical order for deterministic responses.

  **Items:**

  `string`

* **`status`**

  `string`, possible values: `"active", "revoked"`

* **`updated_at`**

  `string`, format: `date-time` — Timestamp of the last update to name or scopes. Omitted if the key has never been updated.

**Example:**

```json
{
  "key_id": "",
  "key_prefix": "",
  "name": "",
  "scopes": [
    ""
  ],
  "status": "active",
  "created_at": "",
  "updated_at": "",
  "last_used_at": ""
}
```

### CreateApiKeyData

- **Type:**

**Example:**

### CreateApiKeyResponse

- **Type:**`object`

* **`data`**

  `object`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "key_id": "",
    "key_prefix": "",
    "name": "",
    "scopes": [
      ""
    ],
    "status": "active",
    "created_at": "",
    "updated_at": "",
    "last_used_at": "",
    "api_key": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

### ApiKeyResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`created_at`**

    `string`, format: `date-time`

  - **`key_id`**

    `string`

  - **`key_prefix`**

    `string` — First 12 characters of the key for identification. Includes the environment prefix (e.g., "lv\_live\_a3x9").

  - **`last_used_at`**

    `string`, format: `date-time`

  - **`name`**

    `string`

  - **`scopes`**

    `array` — Permissions granted to this API key. Returned in alphabetical order for deterministic responses.

    **Items:**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`updated_at`**

    `string`, format: `date-time` — Timestamp of the last update to name or scopes. Omitted if the key has never been updated.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "key_id": "",
    "key_prefix": "",
    "name": "",
    "scopes": [
      ""
    ],
    "status": "active",
    "created_at": "",
    "updated_at": "",
    "last_used_at": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

### ApiKeyListResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`created_at`**

    `string`, format: `date-time`

  - **`key_id`**

    `string`

  - **`key_prefix`**

    `string` — First 12 characters of the key for identification. Includes the environment prefix (e.g., "lv\_live\_a3x9").

  - **`last_used_at`**

    `string`, format: `date-time`

  - **`name`**

    `string`

  - **`scopes`**

    `array` — Permissions granted to this API key. Returned in alphabetical order for deterministic responses.

    **Items:**

    `string`

  - **`status`**

    `string`, possible values: `"active", "revoked"`

  - **`updated_at`**

    `string`, format: `date-time` — Timestamp of the last update to name or scopes. Omitted if the key has never been updated.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "key_id": "",
      "key_prefix": "",
      "name": "",
      "scopes": [
        ""
      ],
      "status": "active",
      "created_at": "",
      "updated_at": "",
      "last_used_at": ""
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

### RedeemAccessCodeRequest

- **Type:**`object`

* **`code` (required)**

  `string` — The plaintext access code to redeem

* **`elapsed_ms`**

  `integer` — Milliseconds elapsed since page load (for bot detection)

* **`honeypot`**

  `string`, default: `""` — Honeypot field for bot detection (must be empty)

**Example:**

```json
{
  "code": "ac_k8xqp9h2sj9lx7r4abcdef",
  "honeypot": "",
  "elapsed_ms": 5200
}
```

### RedeemAccessCodeData

- **Type:**`object`

* **`redirect_url`**

  `string`, format: `uri` — URL to redirect the user to for resource access

**Example:**

```json
{
  "redirect_url": "https://qurl.link/#at_k8xqp9h2sj9lx7r4abcdef"
}
```

### RedeemAccessCodeResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`redirect_url`**

    `string`, format: `uri` — URL to redirect the user to for resource access

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "redirect_url": "https://qurl.link/#at_k8xqp9h2sj9lx7r4abcdef"
  },
  "meta": {
    "request_id": ""
  }
}
```

### CreateAccessCodeRequest

- **Type:**`object`

* **`resource_id` (required)**

  `string` — ID of the qURL resource this code grants access to

* **`expires_at`**

  `string`, format: `date-time` — Optional expiration time for the access code

* **`max_uses`**

  `integer`, default: `0` — Maximum number of times this code can be redeemed (0 = unlimited)

* **`name`**

  `string` — Human-readable label for the access code

**Example:**

```json
{
  "resource_id": "r_k8xqp9h2sj9",
  "name": "Investor Stats Q1",
  "max_uses": 10,
  "expires_at": ""
}
```

### AccessCodeData

- **Type:**`object`

* **`access_code_id`**

  `string` — Unique identifier for the access code

* **`created_at`**

  `string`, format: `date-time`

* **`expires_at`**

  `string | null`, format: `date-time`

* **`max_uses`**

  `integer` — Maximum redemptions allowed (0 = unlimited)

* **`name`**

  `string` — Human-readable label

* **`resource_id`**

  `string` — ID of the qURL resource this code grants access to

* **`status`**

  `string`, possible values: `"active", "revoked"` — Current status of the access code

* **`use_count`**

  `integer` — Number of times this code has been redeemed

**Example:**

```json
{
  "access_code_id": "acd_k8xqp9h2sj9",
  "resource_id": "r_k8xqp9h2sj9",
  "name": "Investor Stats Q1",
  "status": "active",
  "max_uses": 10,
  "use_count": 3,
  "created_at": "",
  "expires_at": null
}
```

### CreateAccessCodeData

- **Type:**

**Example:**

### CreateAccessCodeResponse

- **Type:**`object`

* **`data`**

  `object`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "access_code_id": "acd_k8xqp9h2sj9",
    "resource_id": "r_k8xqp9h2sj9",
    "name": "Investor Stats Q1",
    "status": "active",
    "max_uses": 10,
    "use_count": 3,
    "created_at": "",
    "expires_at": null,
    "code": "ac_k8xqp9h2sj9lx7r4abcdef"
  },
  "meta": {
    "request_id": ""
  }
}
```

### AccessCodeListResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`access_code_id`**

    `string` — Unique identifier for the access code

  - **`created_at`**

    `string`, format: `date-time`

  - **`expires_at`**

    `string | null`, format: `date-time`

  - **`max_uses`**

    `integer` — Maximum redemptions allowed (0 = unlimited)

  - **`name`**

    `string` — Human-readable label

  - **`resource_id`**

    `string` — ID of the qURL resource this code grants access to

  - **`status`**

    `string`, possible values: `"active", "revoked"` — Current status of the access code

  - **`use_count`**

    `integer` — Number of times this code has been redeemed

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": [
    {
      "access_code_id": "acd_k8xqp9h2sj9",
      "resource_id": "r_k8xqp9h2sj9",
      "name": "Investor Stats Q1",
      "status": "active",
      "max_uses": 10,
      "use_count": 3,
      "created_at": "",
      "expires_at": null
    }
  ],
  "meta": {
    "request_id": ""
  }
}
```

### WebhookEventType

- **Type:**`string`

Webhook event types:

- **qurl.created** - A new qURL was created
- **qurl.expired** - A qURL has expired
- **qurl.revoked** - A qURL was manually revoked
- **qurl.updated** - A qURL was updated (expiration, tags, or description)
- **qurl.accessed** - A qURL was successfully accessed. For connector-owned resources (`type=transit`), viewer-identifying fields (`src_ip`, `user_agent`) are omitted from the payload. See `WebhookPayload` description for the full per-event redaction rules.
- **qurl.access\_denied** - Access to a qURL was denied by policy
- **qurl.token\_exhausted** - A one-time-use token was fully consumed
- **quota.warning** - Approaching quota limit (80%)
- **quota.exceeded** - Quota limit reached, action blocked
- **token.minted** - A new access token was created
- **token.expired** - An access token has expired
- **domain.verified** - A custom domain was successfully verified
- **domain.failed** - Custom domain verification or cert provisioning failed
- **domain.deleted** - A custom domain was deleted

**Example:**

### CreateWebhookRequest

- **Type:**`object`

* **`events` (required)**

  `array` — Event types to subscribe to

  **Items:**

  `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

* **`url` (required)**

  `string`, format: `uri` — HTTPS endpoint URL to receive webhook events (max 2048 characters)

* **`description`**

  `string` — Human-readable description

**Example:**

```json
{
  "url": "https://example.com/webhooks/qurl",
  "events": [
    "qurl.created"
  ],
  "description": ""
}
```

### UpdateWebhookRequest

- **Type:**`object`

* **`description`**

  `string`

* **`events`**

  `array`

  **Items:**

  `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

* **`status`**

  `string`, possible values: `"active", "disabled"`

* **`url`**

  `string`, format: `uri`

**Example:**

```json
{
  "url": "",
  "events": [
    "qurl.created"
  ],
  "description": "",
  "status": "active"
}
```

### WebhookData

- **Type:**`object`

* **`created_at`**

  `string`, format: `date-time`

* **`description`**

  `string`

* **`events`**

  `array`

  **Items:**

  `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

* **`failure_count`**

  `integer` — Consecutive delivery failures

* **`last_delivery_success`**

  `boolean`

* **`last_delivery_time`**

  `integer` — Unix timestamp of last delivery attempt

* **`status`**

  `string`, possible values: `"active", "disabled"`

* **`updated_at`**

  `string`, format: `date-time`

* **`url`**

  `string`, format: `uri`

* **`webhook_id`**

  `string`

**Example:**

```json
{
  "webhook_id": "",
  "url": "",
  "events": [
    "qurl.created"
  ],
  "status": "active",
  "description": "",
  "created_at": "",
  "updated_at": "",
  "failure_count": 1,
  "last_delivery_success": true,
  "last_delivery_time": 1
}
```

### WebhookDataWithSecret

- **Type:**

**Example:**

### WebhookResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`created_at`**

    `string`, format: `date-time`

  - **`description`**

    `string`

  - **`events`**

    `array`

    **Items:**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

  - **`failure_count`**

    `integer` — Consecutive delivery failures

  - **`last_delivery_success`**

    `boolean`

  - **`last_delivery_time`**

    `integer` — Unix timestamp of last delivery attempt

  - **`status`**

    `string`, possible values: `"active", "disabled"`

  - **`updated_at`**

    `string`, format: `date-time`

  - **`url`**

    `string`, format: `uri`

  - **`webhook_id`**

    `string`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "webhook_id": "",
    "url": "",
    "events": [
      "qurl.created"
    ],
    "status": "active",
    "description": "",
    "created_at": "",
    "updated_at": "",
    "failure_count": 1,
    "last_delivery_success": true,
    "last_delivery_time": 1
  },
  "meta": {
    "request_id": ""
  }
}
```

### WebhookResponseWithSecret

- **Type:**`object`

* **`data`**

  `object`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "webhook_id": "",
    "url": "",
    "events": [
      "qurl.created"
    ],
    "status": "active",
    "description": "",
    "created_at": "",
    "updated_at": "",
    "failure_count": 1,
    "last_delivery_success": true,
    "last_delivery_time": 1,
    "secret": "whsec_K8xQp9H2sJ9Lx7R4AaBbCcDdEeFf..."
  },
  "meta": {
    "request_id": ""
  }
}
```

### WebhookListResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`created_at`**

    `string`, format: `date-time`

  - **`description`**

    `string`

  - **`events`**

    `array`

    **Items:**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

  - **`failure_count`**

    `integer` — Consecutive delivery failures

  - **`last_delivery_success`**

    `boolean`

  - **`last_delivery_time`**

    `integer` — Unix timestamp of last delivery attempt

  - **`status`**

    `string`, possible values: `"active", "disabled"`

  - **`updated_at`**

    `string`, format: `date-time`

  - **`url`**

    `string`, format: `uri`

  - **`webhook_id`**

    `string`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "webhook_id": "",
      "url": "",
      "events": [
        "qurl.created"
      ],
      "status": "active",
      "description": "",
      "created_at": "",
      "updated_at": "",
      "failure_count": 1,
      "last_delivery_success": true,
      "last_delivery_time": 1
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

### WebhookDeliveryData

- **Type:**`object`

* **`completed_at`**

  `string`, format: `date-time`

* **`created_at`**

  `string`, format: `date-time`

* **`delivery_id`**

  `string`

* **`duration_ms`**

  `integer`

* **`error_message`**

  `string`

* **`event_type`**

  `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

* **`response_body`**

  `string` — Truncated response body (max 8KB)

* **`response_code`**

  `integer`

* **`retry_count`**

  `integer`

* **`status`**

  `string`, possible values: `"pending", "success", "failed", "retrying", "abandoned"`

* **`webhook_id`**

  `string`

**Example:**

```json
{
  "delivery_id": "",
  "webhook_id": "",
  "event_type": "qurl.created",
  "status": "pending",
  "response_code": 1,
  "response_body": "",
  "error_message": "",
  "duration_ms": 1,
  "retry_count": 1,
  "created_at": "",
  "completed_at": ""
}
```

### WebhookDeliveryListResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`completed_at`**

    `string`, format: `date-time`

  - **`created_at`**

    `string`, format: `date-time`

  - **`delivery_id`**

    `string`

  - **`duration_ms`**

    `integer`

  - **`error_message`**

    `string`

  - **`event_type`**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

  - **`response_body`**

    `string` — Truncated response body (max 8KB)

  - **`response_code`**

    `integer`

  - **`retry_count`**

    `integer`

  - **`status`**

    `string`, possible values: `"pending", "success", "failed", "retrying", "abandoned"`

  - **`webhook_id`**

    `string`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "delivery_id": "",
      "webhook_id": "",
      "event_type": "qurl.created",
      "status": "pending",
      "response_code": 1,
      "response_body": "",
      "error_message": "",
      "duration_ms": 1,
      "retry_count": 1,
      "created_at": "",
      "completed_at": ""
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

### WebhookEventTypeInfo

- **Type:**`object`

* **`category`**

  `string`, possible values: `"resource", "access", "quota", "token"`

* **`description`**

  `string`

* **`type`**

  `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

**Example:**

```json
{
  "type": "qurl.created",
  "category": "resource",
  "description": ""
}
```

### WebhookEventTypesResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`category`**

    `string`, possible values: `"resource", "access", "quota", "token"`

  - **`description`**

    `string`

  - **`type`**

    `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": [
    {
      "type": "qurl.created",
      "category": "resource",
      "description": ""
    }
  ],
  "meta": {
    "request_id": ""
  }
}
```

### WebhookPayload

- **Type:**`object`

Webhook event payload sent to your endpoint. Verify authenticity using the QURL-Signature header.

## Connector-resource payload redaction

For resources created via a consumer connector (e.g., the qURL Discord bot, the qURL S3 connector — modeled as resources with `type=transit`), some events are suppressed entirely and others are delivered with viewer-identifying fields omitted from `data`.

The boundary exists because for connector-owned resources the **account owner** (the person who installed the connector and holds the API key) is NOT the same person as the **viewer** (a Discord guild member, a Slack workspace user, etc. who clicked the link). Forwarding viewer IP / User-Agent / filename / target URL to the owner's webhook would let them de-anonymize their members' activity.

### `qurl.accessed` on connector resources

Delivered, but with `src_ip` and `user_agent` OMITTED from `data`. The remaining fields (`qurl_id`, `resource_id`, `access_count`, `consumed`) are safe — the account owner already sees these via `GET /v1/qurls/{id}`.

### Other events on connector resources

`qurl.created`, `qurl.updated`, `qurl.revoked`, `token.minted` are suppressed entirely for connector resources — their payloads include `target_url` and `description` (which the connector populates with the original filename) and that combination would leak the file metadata via the webhook even though the dashboard strip hides it.

- **`api_version`**

  `string` — API version (e.g., "2024-01-01")

- **`data`**

  `object` — Event-specific data. Field set varies by event type AND by resource type. See the schema description for the connector-resource redaction rules and per-event field listings below. ### \`qurl.accessed\` data fields Always present: - \`qurl\_id\` (string) — qURL primary key - \`resource\_id\` (string) — resource primary key - \`access\_count\` (integer) — projected access count after this access. For multi-use qURLs under concurrent load the value is best-effort: two near-simultaneous accesses may both observe the same \`access\_count\`. Subscribers that need an exact total should reconcile from \`GET /v1/qurls/{id}\`. - \`consumed\` (boolean) — whether the access consumed a one-time-use token Present only for \`type=url\` and \`type=tunnel\` resources (OMITTED for \`type=transit\` — connector-resource redaction): - \`src\_ip\` (string) — client IP that accessed the qURL - \`user\_agent\` (string) — viewer's User-Agent header #### \`data\` shape examples URL / tunnel resource (full payload): \`\`\`json { "qurl\_id": "q\_xyz789abcdef", "resource\_id": "r\_k8xqp9h2sj9", "access\_count": 3, "consumed": false, "src\_ip": "192.168.1.100", "user\_agent": "Mozilla/5.0..." } \`\`\` Transit (connector-owned) resource — \`src\_ip\` and \`user\_agent\` omitted: \`\`\`json { "qurl\_id": "q\_abc456def789", "resource\_id": "r\_p3wv8z9q4t2", "access\_count": 7, "consumed": false } \`\`\`

- **`id`**

  `string` — Unique event ID

- **`owner_id`**

  `string` — Owner who triggered the event

- **`timestamp`**

  `string`, format: `date-time`

- **`type`**

  `string`, possible values: `"qurl.created", "qurl.expired", "qurl.revoked", "qurl.updated", "qurl.accessed", "qurl.access_denied", "qurl.token_exhausted", "quota.warning", "quota.exceeded", "token.minted", "token.expired", "domain.verified", "domain.failed", "domain.deleted"` — Webhook event types: - \*\*qurl.created\*\* - A new qURL was created - \*\*qurl.expired\*\* - A qURL has expired - \*\*qurl.revoked\*\* - A qURL was manually revoked - \*\*qurl.updated\*\* - A qURL was updated (expiration, tags, or description) - \*\*qurl.accessed\*\* - A qURL was successfully accessed. For connector-owned resources (\`type=transit\`), viewer-identifying fields (\`src\_ip\`, \`user\_agent\`) are omitted from the payload. See \`WebhookPayload\` description for the full per-event redaction rules. - \*\*qurl.access\_denied\*\* - Access to a qURL was denied by policy - \*\*qurl.token\_exhausted\*\* - A one-time-use token was fully consumed - \*\*quota.warning\*\* - Approaching quota limit (80%) - \*\*quota.exceeded\*\* - Quota limit reached, action blocked - \*\*token.minted\*\* - A new access token was created - \*\*token.expired\*\* - An access token has expired - \*\*domain.verified\*\* - A custom domain was successfully verified - \*\*domain.failed\*\* - Custom domain verification or cert provisioning failed - \*\*domain.deleted\*\* - A custom domain was deleted

**Example:**

```json
{
  "id": "evt_abc123def456",
  "type": "qurl.accessed",
  "owner_id": "auth0|user123",
  "timestamp": "2024-01-08T10:35:00Z",
  "api_version": "2024-01-01",
  "data": {
    "qurl_id": "q_xyz789abcdef",
    "resource_id": "r_k8xqp9h2sj9",
    "access_count": 3,
    "consumed": false,
    "src_ip": "192.168.1.100",
    "user_agent": "Mozilla/5.0..."
  }
}
```

### CustomerData

- **Type:**`object`

* **`current_period_usage` (required)**

  `integer`, format: `int64` — Usage count in current billing period

* **`frozen` (required)**

  `boolean` — Whether the account is frozen

* **`spending_cap_cents` (required)**

  `integer`, format: `int64` — Spending cap in cents (0 = no cap)

* **`tier` (required)**

  `string`, possible values: `"free", "growth", "enterprise"` — Customer's billing tier

* **`frozen_reason`**

  `string | null`, possible values: `"spending_cap", "payment_failed", "manual"` — Reason for account freeze

**Example:**

```json
{
  "tier": "free",
  "spending_cap_cents": 1,
  "current_period_usage": 1,
  "frozen": true,
  "frozen_reason": "spending_cap"
}
```

### CustomerResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`current_period_usage` (required)**

    `integer`, format: `int64` — Usage count in current billing period

  - **`frozen` (required)**

    `boolean` — Whether the account is frozen

  - **`spending_cap_cents` (required)**

    `integer`, format: `int64` — Spending cap in cents (0 = no cap)

  - **`tier` (required)**

    `string`, possible values: `"free", "growth", "enterprise"` — Customer's billing tier

  - **`frozen_reason`**

    `string | null`, possible values: `"spending_cap", "payment_failed", "manual"` — Reason for account freeze

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "tier": "free",
    "spending_cap_cents": 1,
    "current_period_usage": 1,
    "frozen": true,
    "frozen_reason": "spending_cap"
  },
  "meta": {
    "request_id": ""
  }
}
```

### UpdateCustomerRequest

- **Type:**`object`

* **`spending_cap_cents` (required)**

  `integer`, format: `int64` — New spending cap in cents (0 = no cap)

**Example:**

```json
{
  "spending_cap_cents": 0
}
```

### CreateBillingCheckoutRequest

- **Type:**`object`

* **`plan` (required)**

  `string`, possible values: `"growth"` — Paid plan to check out into. Only purchasable plans are permitted (currently: \`growth\`); anything else is rejected as an invalid enum value.

**Example:**

```json
{
  "plan": "growth"
}
```

### CheckoutSessionData

- **Type:**`object`

* **`url` (required)**

  `string`, format: `uri` — Stripe Checkout URL to redirect user to

**Example:**

```json
{
  "url": ""
}
```

### CheckoutSessionResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`url` (required)**

    `string`, format: `uri` — Stripe Checkout URL to redirect user to

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "url": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

### PortalSessionData

- **Type:**`object`

* **`url` (required)**

  `string`, format: `uri` — Stripe Billing Portal URL to redirect user to

**Example:**

```json
{
  "url": ""
}
```

### PortalSessionResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`url` (required)**

    `string`, format: `uri` — Stripe Billing Portal URL to redirect user to

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "url": ""
  },
  "meta": {
    "request_id": ""
  }
}
```

### InvoiceData

- **Type:**`object`

* **`amount_cents` (required)**

  `integer`, format: `int64` — Invoice amount in cents

* **`created_at` (required)**

  `string`, format: `date-time` — When the invoice was created

* **`id` (required)**

  `string` — Stripe invoice ID

* **`status` (required)**

  `string`, possible values: `"paid", "open", "void", "draft"` — Invoice status

* **`pdf_url`**

  `string | null`, format: `uri` — URL to download the invoice PDF

**Example:**

```json
{
  "id": "",
  "amount_cents": 1,
  "status": "paid",
  "created_at": "",
  "pdf_url": null
}
```

### InvoiceListData

- **Type:**`object`

* **`invoices` (required)**

  `array`

  **Items:**

  - **`amount_cents` (required)**

    `integer`, format: `int64` — Invoice amount in cents

  - **`created_at` (required)**

    `string`, format: `date-time` — When the invoice was created

  - **`id` (required)**

    `string` — Stripe invoice ID

  - **`status` (required)**

    `string`, possible values: `"paid", "open", "void", "draft"` — Invoice status

  - **`pdf_url`**

    `string | null`, format: `uri` — URL to download the invoice PDF

**Example:**

```json
{
  "invoices": [
    {
      "id": "",
      "amount_cents": 1,
      "status": "paid",
      "created_at": "",
      "pdf_url": null
    }
  ]
}
```

### InvoiceListResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`invoices` (required)**

    `array`

    **Items:**

    - **`amount_cents` (required)**

      `integer`, format: `int64` — Invoice amount in cents

    - **`created_at` (required)**

      `string`, format: `date-time` — When the invoice was created

    - **`id` (required)**

      `string` — Stripe invoice ID

    - **`status` (required)**

      `string`, possible values: `"paid", "open", "void", "draft"` — Invoice status

    - **`pdf_url`**

      `string | null`, format: `uri` — URL to download the invoice PDF

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": {
    "invoices": [
      {
        "id": "",
        "amount_cents": 1,
        "status": "paid",
        "created_at": "",
        "pdf_url": null
      }
    ]
  },
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

### RegisterDomainRequest

- **Type:**`object`

* **`domain` (required)**

  `string` — The custom domain name to register

**Example:**

```json
{
  "domain": "secure.example.com"
}
```

### DomainData

- **Type:**`object`

* **`acme_cname_target`**

  `string` — CNAME target for ACME certificate provisioning

* **`activated_at`**

  `string | null`, format: `date-time`

* **`created_at`**

  `string`, format: `date-time`

* **`dns_records`**

  `array` — Required DNS records for domain setup

  **Items:**

  - **`name`**

    `string` — DNS record name

  - **`type`**

    `string` — DNS record type (TXT, CNAME)

  - **`value`**

    `string` — DNS record value

  - **`verified`**

    `boolean` — Whether this record has been verified

* **`domain`**

  `string` — The custom domain name

* **`ready_for_qurls`**

  `boolean` — Whether this domain is ready to be used with qURLs (status is verified, provisioning\_tls, or active)

* **`status`**

  `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"` — Current domain status

* **`token_expires_at`**

  `string | null`, format: `date-time` — When the verification token expires. Omitted for domains created before token expiry was introduced (those tokens never expire). After expiry, use the regenerate-token endpoint to get a new one.

* **`verification_token`**

  `string` — TXT record value for DNS verification

* **`verified_at`**

  `string | null`, format: `date-time`

**Example:**

```json
{
  "domain": "",
  "status": "pending_verification",
  "verification_token": "",
  "token_expires_at": null,
  "acme_cname_target": "",
  "created_at": "",
  "verified_at": null,
  "activated_at": null,
  "ready_for_qurls": true,
  "dns_records": [
    {
      "type": "",
      "name": "",
      "value": "",
      "verified": true
    }
  ]
}
```

### DnsRecord

- **Type:**`object`

* **`name`**

  `string` — DNS record name

* **`type`**

  `string` — DNS record type (TXT, CNAME)

* **`value`**

  `string` — DNS record value

* **`verified`**

  `boolean` — Whether this record has been verified

**Example:**

```json
{
  "type": "",
  "name": "",
  "value": "",
  "verified": true
}
```

### DomainResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`acme_cname_target`**

    `string` — CNAME target for ACME certificate provisioning

  - **`activated_at`**

    `string | null`, format: `date-time`

  - **`created_at`**

    `string`, format: `date-time`

  - **`dns_records`**

    `array` — Required DNS records for domain setup

    **Items:**

    - **`name`**

      `string` — DNS record name

    - **`type`**

      `string` — DNS record type (TXT, CNAME)

    - **`value`**

      `string` — DNS record value

    - **`verified`**

      `boolean` — Whether this record has been verified

  - **`domain`**

    `string` — The custom domain name

  - **`ready_for_qurls`**

    `boolean` — Whether this domain is ready to be used with qURLs (status is verified, provisioning\_tls, or active)

  - **`status`**

    `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"` — Current domain status

  - **`token_expires_at`**

    `string | null`, format: `date-time` — When the verification token expires. Omitted for domains created before token expiry was introduced (those tokens never expire). After expiry, use the regenerate-token endpoint to get a new one.

  - **`verification_token`**

    `string` — TXT record value for DNS verification

  - **`verified_at`**

    `string | null`, format: `date-time`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "domain": "",
    "status": "pending_verification",
    "verification_token": "",
    "token_expires_at": null,
    "acme_cname_target": "",
    "created_at": "",
    "verified_at": null,
    "activated_at": null,
    "ready_for_qurls": true,
    "dns_records": [
      {
        "type": "",
        "name": "",
        "value": "",
        "verified": true
      }
    ]
  },
  "meta": {
    "request_id": ""
  }
}
```

### DomainListResponse

- **Type:**`object`

* **`data`**

  `array`

  **Items:**

  - **`acme_cname_target`**

    `string` — CNAME target for ACME certificate provisioning

  - **`activated_at`**

    `string | null`, format: `date-time`

  - **`created_at`**

    `string`, format: `date-time`

  - **`dns_records`**

    `array` — Required DNS records for domain setup

    **Items:**

    - **`name`**

      `string` — DNS record name

    - **`type`**

      `string` — DNS record type (TXT, CNAME)

    - **`value`**

      `string` — DNS record value

    - **`verified`**

      `boolean` — Whether this record has been verified

  - **`domain`**

    `string` — The custom domain name

  - **`ready_for_qurls`**

    `boolean` — Whether this domain is ready to be used with qURLs (status is verified, provisioning\_tls, or active)

  - **`status`**

    `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"` — Current domain status

  - **`token_expires_at`**

    `string | null`, format: `date-time` — When the verification token expires. Omitted for domains created before token expiry was introduced (those tokens never expire). After expiry, use the regenerate-token endpoint to get a new one.

  - **`verification_token`**

    `string` — TXT record value for DNS verification

  - **`verified_at`**

    `string | null`, format: `date-time`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string`

  - **`has_more`**

    `boolean` — Whether more items exist

  - **`next_cursor`**

    `string` — Cursor for next page (empty if no more pages)

  - **`page_size`**

    `integer` — Number of items returned

**Example:**

```json
{
  "data": [
    {
      "domain": "",
      "status": "pending_verification",
      "verification_token": "",
      "token_expires_at": null,
      "acme_cname_target": "",
      "created_at": "",
      "verified_at": null,
      "activated_at": null,
      "ready_for_qurls": true,
      "dns_records": [
        {
          "type": "",
          "name": "",
          "value": "",
          "verified": true
        }
      ]
    }
  ],
  "meta": {
    "request_id": "",
    "page_size": 1,
    "has_more": true,
    "next_cursor": ""
  }
}
```

### CheckDetail

- **Type:**`object`

* **`verified` (required)**

  `boolean`

* **`error`**

  `string` — Error message when the check fails (omitted when verified).

* **`found`**

  `string` — The actual value found in DNS (omitted when nothing was found).

**Example:**

```json
{
  "verified": true,
  "error": "",
  "found": ""
}
```

### DomainVerifyData

- **Type:**`object`

* **`checks`**

  `object`

  - **`acme_cname`**

    `object`

    - **`verified` (required)**

      `boolean`

    - **`error`**

      `string` — Error message when the check fails (omitted when verified).

    - **`found`**

      `string` — The actual value found in DNS (omitted when nothing was found).

  - **`traffic_routing`**

    `object`

    - **`verified` (required)**

      `boolean`

    - **`error`**

      `string` — Error message when the check fails (omitted when verified).

    - **`found`**

      `string` — The actual value found in DNS (omitted when nothing was found).

  - **`txt`**

    `object`

    - **`verified` (required)**

      `boolean`

    - **`error`**

      `string` — Error message when the check fails (omitted when verified).

    - **`found`**

      `string` — The actual value found in DNS (omitted when nothing was found).

* **`domain`**

  `string`

* **`status`**

  `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"`

**Example:**

```json
{
  "domain": "",
  "status": "pending_verification",
  "checks": {
    "txt": {
      "verified": true,
      "error": "",
      "found": ""
    },
    "acme_cname": {
      "verified": true,
      "error": "",
      "found": ""
    },
    "traffic_routing": {
      "verified": true,
      "error": "",
      "found": ""
    }
  }
}
```

### DomainVerifyResponse

- **Type:**`object`

* **`data`**

  `object`

  - **`checks`**

    `object`

    - **`acme_cname`**

      `object`

      - **`verified` (required)**

        `boolean`

      - **`error`**

        `string` — Error message when the check fails (omitted when verified).

      - **`found`**

        `string` — The actual value found in DNS (omitted when nothing was found).

    - **`traffic_routing`**

      `object`

      - **`verified` (required)**

        `boolean`

      - **`error`**

        `string` — Error message when the check fails (omitted when verified).

      - **`found`**

        `string` — The actual value found in DNS (omitted when nothing was found).

    - **`txt`**

      `object`

      - **`verified` (required)**

        `boolean`

      - **`error`**

        `string` — Error message when the check fails (omitted when verified).

      - **`found`**

        `string` — The actual value found in DNS (omitted when nothing was found).

  - **`domain`**

    `string`

  - **`status`**

    `string`, possible values: `"pending_verification", "verified", "provisioning_tls", "active", "failed"`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "domain": "",
    "status": "pending_verification",
    "checks": {
      "txt": {
        "verified": true,
        "error": "",
        "found": ""
      },
      "acme_cname": {
        "verified": true,
        "error": "",
        "found": ""
      },
      "traffic_routing": {
        "verified": true,
        "error": "",
        "found": ""
      }
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

### Meta

- **Type:**`object`

* **`request_id` (required)**

  `string` — Unique request identifier for tracing

**Example:**

```json
{
  "request_id": ""
}
```

### ListMeta

- **Type:**`object`

* **`request_id` (required)**

  `string`

* **`has_more`**

  `boolean` — Whether more items exist

* **`next_cursor`**

  `string` — Cursor for next page (empty if no more pages)

* **`page_size`**

  `integer` — Number of items returned

**Example:**

```json
{
  "request_id": "",
  "page_size": 1,
  "has_more": true,
  "next_cursor": ""
}
```

### Error

- **Type:**`object`

Error response following RFC 7807 Problem Details for HTTP APIs. See: <https://datatracker.ietf.org/doc/html/rfc7807>

- **`code` (required)**

  `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

- **`status` (required)**

  `integer` — HTTP status code for this occurrence (RFC 7807).

- **`title` (required)**

  `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

- **`type` (required)**

  `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

- **`detail`**

  `string` — Human-readable explanation specific to this occurrence (RFC 7807).

- **`instance`**

  `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

**Example:**

```json
{
  "type": "https://api.qurl.link/problems/invalid_request",
  "title": "Invalid Request",
  "status": 400,
  "detail": "The target_url field must be a valid HTTPS URL",
  "instance": "/v1/qurls",
  "code": "invalid_request"
}
```

### ValidationError

- **Type:**

**Example:**

### ErrorEnvelope

- **Type:**`object`

* **`error`**

  `object` — Error response following RFC 7807 Problem Details for HTTP APIs. See: https\://datatracker.ietf.org/doc/html/rfc7807

  - **`code` (required)**

    `string` — Machine-readable error code (extension field for backward compatibility). Maps to the problem type slug.

  - **`status` (required)**

    `integer` — HTTP status code for this occurrence (RFC 7807).

  - **`title` (required)**

    `string` — Short, human-readable summary of the problem type (RFC 7807). This should not change from occurrence to occurrence.

  - **`type` (required)**

    `string`, format: `uri` — URI reference that identifies the problem type (RFC 7807). When dereferenced, it should provide human-readable documentation.

  - **`detail`**

    `string` — Human-readable explanation specific to this occurrence (RFC 7807).

  - **`instance`**

    `string`, format: `uri-reference` — URI reference that identifies the specific occurrence (RFC 7807). Typically the request path.

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request"
  },
  "meta": {
    "request_id": ""
  }
}
```

### ValidationErrorEnvelope

- **Type:**`object`

* **`error`**

  `object`

* **`meta`**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "error": {
    "type": "https://api.qurl.link/problems/invalid_request",
    "title": "Invalid Request",
    "status": 400,
    "detail": "The target_url field must be a valid HTTPS URL",
    "instance": "/v1/qurls",
    "code": "invalid_request",
    "invalid_fields": {
      "target_url": "must be a valid HTTPS URL",
      "description": "must not exceed 500 characters"
    }
  },
  "meta": {
    "request_id": ""
  }
}
```

### AgentBootstrapRequest

- **Type:**`object`

* **`public_key` (required)**

  `string` — Agent's X25519 public key, raw 32 bytes encoded as standard base64 (RFC 4648 §4 — \`+\`/\`/\` alphabet, \`=\` padding).

* **`agent_id`**

  `string` — Optional label correlating this agent across requests (matches \`LAYERV\_AGENT\_ID\` on the agent). The server generates one when omitted.

* **`hostname`**

  `string` — Optional hostname where the agent is running (audit log only). If a previous bootstrap recorded a value, omitting this field (or sending an empty string) preserves the prior value; send a non-empty string to replace it. There is no wire shape to clear a previously-set hostname back to empty — audit lineage is intentionally append-only.

* **`version`**

  `string` — Optional agent build version (audit log only). Same merge behavior as \`hostname\`: omitting or sending empty preserves the prior value; send a non-empty string to replace it. Cannot be cleared back to empty (same audit-lineage rule).

**Example:**

```json
{
  "public_key": "62cFrVBeF1Tl7lUAJ9MNa9lFykVf6D7mNqLaEYggFN0=",
  "agent_id": "prod-us-east-1",
  "hostname": "agent-7f8c9b-prod-use1",
  "version": "1.4.2"
}
```

### AgentBootstrapData

- **Type:**`object`

Bootstrap response payload. An agent's registration may expire after roughly 90 days of inactivity; long-lived agents should re-bootstrap on restart. Re-bootstrapping with the same `agent_id` is safe and returns the original `registered_at`.

- **`agent_id` (required)**

  `string` — Label for the agent; echoes the request value, or the server-generated identifier when none was supplied.

- **`nhp_server_peer` (required)**

  `object` — Peer info the agent needs to perform its first NHP knock.

  - **`expire_time` (required)**

    `integer`, format: `int64` — Unix timestamp (seconds) at which this peer entry should be considered stale, or 0 for "no client-side expiry".

  - **`host` (required)**

    `string` — NHP server hostname or IP (no scheme).

  - **`port` (required)**

    `integer` — NHP server UDP port.

  - **`public_key_b64` (required)**

    `string` — NHP server X25519 public key, base64-encoded (32 raw bytes).

- **`registered_at` (required)**

  `string`, format: `date-time` — RFC3339 timestamp of the first registration for this \`agent\_id\`. Re-bootstraps return the original value, not the current time.

**Example:**

```json
{
  "agent_id": "",
  "registered_at": "",
  "nhp_server_peer": {
    "public_key_b64": "",
    "host": "nhp.layerv.ai",
    "port": 62206,
    "expire_time": 0
  }
}
```

### NHPServerPeerInfo

- **Type:**`object`

Peer info the agent needs to perform its first NHP knock.

- **`expire_time` (required)**

  `integer`, format: `int64` — Unix timestamp (seconds) at which this peer entry should be considered stale, or 0 for "no client-side expiry".

- **`host` (required)**

  `string` — NHP server hostname or IP (no scheme).

- **`port` (required)**

  `integer` — NHP server UDP port.

- **`public_key_b64` (required)**

  `string` — NHP server X25519 public key, base64-encoded (32 raw bytes).

**Example:**

```json
{
  "public_key_b64": "",
  "host": "nhp.layerv.ai",
  "port": 62206,
  "expire_time": 0
}
```

### AgentBootstrapResponse

- **Type:**`object`

* **`data` (required)**

  `object` — Bootstrap response payload. An agent's registration may expire after roughly 90 days of inactivity; long-lived agents should re-bootstrap on restart. Re-bootstrapping with the same \`agent\_id\` is safe and returns the original \`registered\_at\`.

  - **`agent_id` (required)**

    `string` — Label for the agent; echoes the request value, or the server-generated identifier when none was supplied.

  - **`nhp_server_peer` (required)**

    `object` — Peer info the agent needs to perform its first NHP knock.

    - **`expire_time` (required)**

      `integer`, format: `int64` — Unix timestamp (seconds) at which this peer entry should be considered stale, or 0 for "no client-side expiry".

    - **`host` (required)**

      `string` — NHP server hostname or IP (no scheme).

    - **`port` (required)**

      `integer` — NHP server UDP port.

    - **`public_key_b64` (required)**

      `string` — NHP server X25519 public key, base64-encoded (32 raw bytes).

  - **`registered_at` (required)**

    `string`, format: `date-time` — RFC3339 timestamp of the first registration for this \`agent\_id\`. Re-bootstraps return the original value, not the current time.

* **`meta` (required)**

  `object`

  - **`request_id` (required)**

    `string` — Unique request identifier for tracing

**Example:**

```json
{
  "data": {
    "agent_id": "",
    "registered_at": "",
    "nhp_server_peer": {
      "public_key_b64": "",
      "host": "nhp.layerv.ai",
      "port": 62206,
      "expire_time": 0
    }
  },
  "meta": {
    "request_id": ""
  }
}
```
