{"openapi":"3.0.0","paths":{"/api/v1/users":{"get":{"description":"  \nLists users inside the caller's tenant. Supports page-based pagination, status filtering, name/email search, and sorting.\n  \n**Required scope:** `users:read`.\n  \n**Pagination:** `page` (1-based) + `limit`. Read `pagination.total` / `pagination.pages` to walk the result. Page-based pagination has the same drift caveat as any `OFFSET` query — rows inserted between page fetches may shift items by one position.\n  \n**Sorting:** defaults to `created_at desc`. Pass `sort_by` (`created_at` or `name`) and `sort_order` (`asc` or `desc`) to override.\n  \n**Rate limiting:** subject to the standard public API limits. See the `429` response.\n  ","operationId":"PublicUsersController_listUsers","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"1-based page number. Use together with `limit` to walk the list.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/users`."},"schema":{"minimum":1,"maximum":9007199254740991,"default":1,"example":1,"type":"integer"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of users to return per page.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/users`."},"schema":{"minimum":1,"maximum":100,"default":20,"example":20,"type":"integer"}},{"name":"status","required":false,"in":"query","description":"Filter by user status. Omit to return users in any status. `active` returns approved users; `blocked` returns users blocked by the tenant. To find users mid-onboarding, omit the filter and inspect each item's `status` field for `pending`.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/users`."},"schema":{"example":"active","type":"string","enum":["active","blocked"]}},{"name":"search","required":false,"in":"query","description":"Case-insensitive substring search across user name and email.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/users`."},"schema":{"maxLength":200,"example":"alex","type":"string"}},{"name":"sort_by","required":false,"in":"query","description":"Field to sort by.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/users`."},"schema":{"default":"created_at","example":"created_at","type":"string","enum":["created_at","name"]}},{"name":"sort_order","required":false,"in":"query","description":"Sort order.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/users`."},"schema":{"default":"desc","example":"desc","type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"Page of users in the caller's tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/List Users Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"422":{"description":"  \nRequest body or query parameters failed validation. Body uses error code `validation_failed` and `details` is an array of `{ field, issue }` entries pointing to the offending fields.\n  ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Public Api Error Envelope"},{"type":"object","properties":{"error":{"type":"object","properties":{"details":{"type":"array","items":{"$ref":"#/components/schemas/Public Api Validation Detail"}}}}}}]}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"List users","tags":["Agency / Users"]},"post":{"description":"  \nProvisions a new Dialora user inside the caller's tenant.\n  \n**Required scope:** `users:write`.\n  \n**Side effects:** creates a `User` record and triggers a welcome email if the tenant has an email provider configured.\n  \n**Idempotency:** pass the `Idempotency-Key` header to make retries safe. Replays with the same key + identical request body return the original `201` response. Without the header, a retry with the same email returns `409 conflict`.\n  \n**Response headers:** the `Location` header points at the newly-created resource.\n  ","operationId":"PublicUsersController_createUser","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"  \nOptional client-supplied key (`≤255` printable ASCII chars) used to deduplicate retries within a 24-hour window. Replays with the same key + identical request body return the original response. Reuse with a different body returns `409 idempotency_key_reuse`.\n  ","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Attributes for the user to create.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Create User Request"},"examples":{"minimal":{"summary":"Minimum required fields","value":{"email":"partner-user@example.com","name":"Alex Partner","password":"hunter2hunter2"}},"with_phone":{"summary":"Including optional phone","value":{"email":"partner-user@example.com","name":"Alex Partner","password":"hunter2hunter2","phone":"+15551234567"}}}}}},"responses":{"201":{"description":"User created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Create User Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"409":{"description":"A user with this email already exists in the tenant, or the Idempotency-Key was reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"422":{"description":"  \nRequest body or query parameters failed validation. Body uses error code `validation_failed` and `details` is an array of `{ field, issue }` entries pointing to the offending fields.\n  ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Public Api Error Envelope"},{"type":"object","properties":{"error":{"type":"object","properties":{"details":{"type":"array","items":{"$ref":"#/components/schemas/Public Api Validation Detail"}}}}}}]}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"Create a user","tags":["Agency / Users"]}},"/api/v1/users/{id}":{"delete":{"description":"  \nSoft-deletes a Dialora user inside the caller's tenant. Revokes the user's sessions and removes their tenant memberships.\n  \n**Required scope:** `users:write`.\n  \n**Idempotency:** naturally idempotent. The first call and replays both return `200` with the tombstone state. `404 not_found` is returned only when the user never existed in the caller's tenant.\n  ","operationId":"PublicUsersController_deleteUser","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"ID of the user to delete.","schema":{"example":"user_ck9z3...","type":"string"}}],"responses":{"200":{"description":"User deleted (tombstone state).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delete User Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"404":{"description":"User not found in the caller's tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"Delete a user","tags":["Agency / Users"]}},"/api/v1/plans":{"get":{"description":"  \nLists subscription plans in the caller's tenant catalog. Returns both Stripe-backed plans and offline-billed (`agency`) plans — branch on the `provider` field.\n  \n**Required scope:** `plans:read`.\n  \n**Filters:** `active` defaults to `true` (only plans offered to new subscribers). Pass `active=false` to include archived plans alongside active ones.\n  \n**Sorting:** defaults to `created_at desc`. Override with `sort_by` (`created_at` or `name`) and `sort_order` (`asc` or `desc`).\n  \n**Rate limiting:** subject to the standard public API limits. See the `429` response.\n  ","operationId":"PublicPlansController_listPlans","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"1-based page number.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/plans`."},"schema":{"minimum":1,"maximum":9007199254740991,"default":1,"example":1,"type":"integer"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of plans per page.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/plans`."},"schema":{"minimum":1,"maximum":100,"default":20,"example":20,"type":"integer"}},{"name":"active","required":false,"in":"query","description":"When `true` (default), returns only plans currently offered to new subscribers. Set to `false` to include archived/inactive plans alongside active ones.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/plans`."},"schema":{"example":true,"type":"string"}},{"name":"sort_by","required":false,"in":"query","description":"Field to sort by.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/plans`."},"schema":{"default":"created_at","example":"created_at","type":"string","enum":["created_at","name"]}},{"name":"sort_order","required":false,"in":"query","description":"Sort order.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/plans`."},"schema":{"default":"desc","example":"desc","type":"string","enum":["asc","desc"]}},{"name":"provider","required":false,"in":"query","description":"Filter by provider.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/plans`."},"schema":{"example":"stripe","type":"string","enum":["stripe","agency"]}}],"responses":{"200":{"description":"Page of plans in the caller's tenant catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/List Plans Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"422":{"description":"  \nRequest body or query parameters failed validation. Body uses error code `validation_failed` and `details` is an array of `{ field, issue }` entries pointing to the offending fields.\n  ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Public Api Error Envelope"},{"type":"object","properties":{"error":{"type":"object","properties":{"details":{"type":"array","items":{"$ref":"#/components/schemas/Public Api Validation Detail"}}}}}}]}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"List plans","tags":["Agency / Plans"]}},"/api/v1/plans/{id}":{"get":{"description":"  \nFetch a single subscription plan by ID inside the caller's tenant catalog.\n  \n**Required scope:** `plans:read`.\n  \n**Errors:**\n  \n- `404 not_found` — plan does not exist in the caller's tenant, or has been archived/soft-deleted.\n  ","operationId":"PublicPlansController_getPlan","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"ID of the plan to fetch.","schema":{"example":"0c2a8c2a-1b3d-4e5f-9a0b-1234567890ab","type":"string"}}],"responses":{"200":{"description":"The requested plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Get Plan Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"404":{"description":"Plan not found in the caller's tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"Get a plan","tags":["Agency / Plans"]}},"/api/v1/subscriptions":{"post":{"description":"  \nSubscribes a user to a plan. If the user already has an active or trialing subscription on a different plan, this endpoint switches them (Stripe handles proration).\n  \n**Required scope:** `subscriptions:write`.\n  \n**Idempotency:** pass the `Idempotency-Key` header. The value is forwarded to Stripe so retries of the same request produce a single billing side effect. Replays with the same key + identical body return the original response.\n  \n**Response headers:** the `Location` header points at the newly-created resource.\n  \n**Errors:**\n  \n- `404 not_found` — `user_id` or `plan_id` does not exist in the caller's tenant.\n- `422 unprocessable_entity` — plan is not active, provider/currency mismatch with the user's current plan, user is blocked, or the user has no payment method on file.\n- `409 idempotency_key_reuse` — `Idempotency-Key` was reused with a different request body.\n  ","operationId":"PublicSubscriptionsController_createSubscription","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"  \nOptional client-supplied key (`≤255` printable ASCII chars) used to deduplicate retries within a 24-hour window. Replays with the same key + identical request body return the original response. Reuse with a different body returns `409 idempotency_key_reuse`.\n  ","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Subscription target.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Create Subscription Request"},"examples":{"growth":{"summary":"Subscribe a user to the Growth plan","value":{"user_id":"user_ck9z3...","plan_id":"0c2a8c2a-1b3d-4e5f-9a0b-1234567890ab"}}}}}},"responses":{"201":{"description":"Subscription created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Create Subscription Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"404":{"description":"User or plan not found in the caller's tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"409":{"description":"Idempotency-Key was reused with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"422":{"description":"  \nRequest body or query parameters failed validation. Body uses error code `validation_failed` and `details` is an array of `{ field, issue }` entries pointing to the offending fields.\n  ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Public Api Error Envelope"},{"type":"object","properties":{"error":{"type":"object","properties":{"details":{"type":"array","items":{"$ref":"#/components/schemas/Public Api Validation Detail"}}}}}}]}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"Create a subscription","tags":["Agency / Subscriptions"]},"get":{"description":"  \nLists subscriptions inside the caller's tenant. Supports page-based pagination and filters by `user_id`, `plan_id`, and `status`.\n  \n**Required scope:** `subscriptions:read`.\n  \n**Sorting:** by `created_at`. Pass `sort_order` (`asc` or `desc`, default `desc`).\n  ","operationId":"PublicSubscriptionsController_listSubscriptions","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"1-based page number.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/subscriptions`."},"schema":{"minimum":1,"maximum":9007199254740991,"default":1,"example":1,"type":"integer"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of subscriptions per page.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/subscriptions`."},"schema":{"minimum":1,"maximum":100,"default":20,"example":20,"type":"integer"}},{"name":"user_id","required":false,"in":"query","description":"Filter to subscriptions belonging to this user.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/subscriptions`."},"schema":{"example":"user_ck9z3...","type":"string"}},{"name":"plan_id","required":false,"in":"query","description":"Filter to subscriptions on this plan.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/subscriptions`."},"schema":{"example":"plan_uuid","type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by lifecycle state.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/subscriptions`."},"schema":{"example":"active","type":"string","enum":["trialing","active","past_due","canceled","unpaid","paused","inactive"]}},{"name":"sort_order","required":false,"in":"query","description":"Sort by `created_at` in the chosen order.","x-nestjs_zod-parent-metadata":{"description":"Query parameters for `GET /api/v1/subscriptions`."},"schema":{"default":"desc","example":"desc","type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"Page of subscriptions in the caller's tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/List Subscriptions Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"422":{"description":"  \nRequest body or query parameters failed validation. Body uses error code `validation_failed` and `details` is an array of `{ field, issue }` entries pointing to the offending fields.\n  ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Public Api Error Envelope"},{"type":"object","properties":{"error":{"type":"object","properties":{"details":{"type":"array","items":{"$ref":"#/components/schemas/Public Api Validation Detail"}}}}}}]}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"List subscriptions","tags":["Agency / Subscriptions"]}},"/api/v1/subscriptions/{id}":{"get":{"description":"  \nFetch a single subscription by ID inside the caller's tenant.\n  \n**Required scope:** `subscriptions:read`.\n  ","operationId":"PublicSubscriptionsController_getSubscription","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Subscription ID.","schema":{"example":"sub_ck9z3...","type":"string"}}],"responses":{"200":{"description":"The requested subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Get Subscription Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"404":{"description":"Subscription not found in the caller's tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"Get a subscription","tags":["Agency / Subscriptions"]}},"/api/v1/subscriptions/{id}/plan":{"patch":{"description":"  \nSwitches an existing subscription to a different plan in the caller's catalog. Behaves as an **upgrade** when the new plan's monthly price is greater than or equal to the current plan, and as a **downgrade** otherwise. The action is computed server-side from the price comparison.\n  \n**Required scope:** `subscriptions:write`.\n  \n**Stripe-billed subscriptions:** the value of `Idempotency-Key` is forwarded to Stripe's `subscriptions.update` call. Upgrades use `proration_behavior: always_invoice` and may produce an unpaid invoice; the partner can detect \"payment required\" from the `status` field on the returned subscription (`unpaid` / `past_due`).\n  \n**Agency-billed subscriptions:** upgrades adjust the active plan and allocate the price-difference credit for the current period. Downgrades cap the current subscription at end of month and create a new subscription on the target plan starting one minute after — the response `data` is the **new** subscription record.\n  \n**Errors:**\n  \n- `404 not_found` — subscription does not exist in the caller's tenant, or target `plan_id` is not an active plan in the catalog.\n- `422 unprocessable_entity` — already on the target plan, provider mismatch (stripe ↔ agency), currency mismatch, user blocked, or no payment method on file.\n- `409 idempotency_key_reuse` — `Idempotency-Key` was reused with a different request body.\n  \n**Learn more:** [Sub-account plan change](https://www.dialora.ai/docs/agency/sub-account-plan-change) — proration, billing-period boundaries, recovering from `past_due`, and partner-side patterns for handling invoice URLs.\n  ","operationId":"PublicSubscriptionsController_changeSubscriptionPlan","parameters":[{"name":"X-Request-Id","in":"header","description":"  \nReturned on every response and echoed in the error envelope's `request_id`. Include in support tickets.\n  ","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"ID of the subscription to switch.","schema":{"example":"sub_ck9z3...","type":"string"}},{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"  \nOptional client-supplied key (`≤255` printable ASCII chars) used to deduplicate retries within a 24-hour window. Replays with the same key + identical request body return the original response. Reuse with a different body returns `409 idempotency_key_reuse`.\n  ","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Target plan to switch the subscription to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Change Subscription Plan Request"},"examples":{"growth":{"summary":"Switch to the Growth plan","value":{"plan_id":"0c2a8c2a-1b3d-4e5f-9a0b-1234567890ab"}}}}}},"responses":{"200":{"description":"Updated subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Change Subscription Plan Response"}}}},"401":{"description":"  \nMissing, malformed, revoked, expired, or unrecognized API key. Body uses error code `authentication_failed`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"403":{"description":"  \nCredential is valid but lacks the required scope. Body uses error code `permission_denied` and includes `details.required_scopes`.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"404":{"description":"Subscription or target plan not found in the caller's tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"409":{"description":"Idempotency-Key was reused with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"422":{"description":"  \nRequest body or query parameters failed validation. Body uses error code `validation_failed` and `details` is an array of `{ field, issue }` entries pointing to the offending fields.\n  ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Public Api Error Envelope"},{"type":"object","properties":{"error":{"type":"object","properties":{"details":{"type":"array","items":{"$ref":"#/components/schemas/Public Api Validation Detail"}}}}}}]}}}},"429":{"description":"  \nRate limit exceeded. Body uses error code `rate_limited`. The `Retry-After` response header carries the number of seconds to wait before retrying.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"500":{"description":"  \nUnexpected server error. The `request_id` in the body and the X-Request-Id header identify the failed request for support tickets.\n  ","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}},"503":{"description":"Service is temporarily unavailable. Retry with exponential backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Public Api Error Envelope"}}}}},"security":[{"publicApiKey":[]}],"summary":"Change subscription plan","tags":["Agency / Subscriptions"]}}},"info":{"title":"Dialora Public API","description":"  \nPartner-facing REST API for the Dialora AI Voice Agent Platform.\n  \n**Authentication:** every request must carry a bearer API key issued from the tenant dashboard. Token format is `dlr_<env>_<32 characters>`, where `<env>` is `live` in production and `test` everywhere else.\n  \n**Errors:** all error responses share the same envelope — see the `PublicApiErrorEnvelopeDto` schema in this document. The `error.code` field is the stable contract; `error.message` is human-readable and may change.\n  \n**Request tracing:** every response carries an `X-Request-Id` header that is echoed inside the error envelope's `request_id` field — include it in support tickets.\n  ","version":"1.0","contact":{}},"tags":[{"name":"Agency / Users","description":"Provision and manage Dialora users inside the caller's tenant."},{"name":"Agency / Plans","description":"Read the subscription plan catalog for the caller's tenant."},{"name":"Agency / Subscriptions","description":"Create, list, and read subscriptions inside the caller's tenant."}],"servers":[{"url":"https://api.dialora.ai","description":"Production"}],"components":{"securitySchemes":{"publicApiKey":{"scheme":"bearer","bearerFormat":"dlr_<env>_<32 chars>","type":"http","description":"  \nBearer API key issued from the tenant dashboard. Format: `dlr_live_<32 chars>` in production, `dlr_test_<32 chars>` everywhere else.\n  "}},"schemas":{"User List Item":{"type":"object","properties":{"id":{"type":"string","example":"user_ck9z3..."},"name":{"type":"string","example":"Alex Partner","nullable":true},"email":{"type":"string","example":"partner-user@example.com"},"phone":{"type":"string","example":"+15551234567","nullable":true},"status":{"type":"string","enum":["active","pending","blocked"],"example":"active","description":"Public user status. `active` (approved), `pending` (mid-onboarding), or `blocked`."},"created_at":{"type":"string","example":"2026-05-12T15:04:05.000Z","description":"ISO 8601 timestamp."},"plan_id":{"type":"string","example":"plan_ck9z3...","description":"Dialora plan ID of the user's current active/trialing plan, or `null` if none.","nullable":true},"plan_name":{"type":"string","example":"Growth","description":"Human-readable name of the user's current plan, or `null` if none.","nullable":true}},"required":["id","name","email","phone","status","created_at","plan_id","plan_name"],"description":"A single user row in `GET /api/v1/users`."},"List Users Pagination":{"type":"object","properties":{"page":{"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"total":{"type":"integer","minimum":0,"maximum":9007199254740991},"pages":{"type":"integer","minimum":0,"maximum":9007199254740991},"has_more":{"type":"boolean"}},"required":["page","limit","total","pages","has_more"],"description":"Pagination block returned alongside `GET /api/v1/users`."},"List Users Response":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/User List Item"}},"pagination":{"$ref":"#/components/schemas/List Users Pagination"}},"description":"Response envelope for `GET /api/v1/users`.","required":["data","pagination"]},"Public Api Error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","validation_failed","authentication_failed","permission_denied","not_found","conflict","idempotency_key_reuse","unprocessable_entity","rate_limited","internal_error","service_unavailable"],"example":"invalid_request","description":"Stable machine-readable error code. Drives all retry / branching logic on the partner side."},"message":{"type":"string","example":"invalid request","description":"Short human-readable summary. Format is not part of the contract — do not parse."},"details":{"description":"  \nOptional structured detail. An array of validation issues for `validation_failed`, or a scope-missing object for `permission_denied`.\n  ","anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Public Api Validation Detail"}},{"$ref":"#/components/schemas/Public Api Scope Detail"}]}},"description":"Machine-readable error payload. `code` is the stable contract; `message` is human-readable and may change; `details` carries error-specific structured context.","required":["code","message"]},"Public Api Validation Detail":{"type":"object","properties":{"field":{"type":"string","example":"email","description":"Name of the offending request field, if known."},"issue":{"type":"string","example":"must be an email","description":"Human-readable explanation of why the field failed validation."}},"description":"One offending field reported inside a `validation_failed` error envelope.","required":["issue"]},"Public Api Scope Detail":{"type":"object","properties":{"required_scopes":{"type":"array","items":{"type":"string"},"example":["users:write"],"description":"Scopes the credential is missing for this request."}},"description":"Detail object attached to `permission_denied` errors. Lists the scopes the credential is missing.","required":["required_scopes"]},"Public Api Error Envelope":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Public Api Error"},"request_id":{"type":"string","example":"req_01H9X8K7Q2N5J3M4P6R8S0T1V2","description":"Echoed in the X-Request-Id response header. Include in support tickets."}},"description":"Standard error envelope returned by every `/api/v1/*` endpoint on non-2xx responses. `request_id` echoes the `X-Request-Id` response header — include it in support tickets.","required":["error","request_id"]},"Create User Request":{"type":"object","properties":{"email":{"example":"partner-user@example.com","description":"Email address for the new user.","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string","minLength":1,"example":"Alex Partner","description":"Display name for the new user."},"password":{"type":"string","minLength":8,"example":"hunter2hunter2","description":"Initial password (min 8 chars)."},"phone":{"example":"+15551234567","description":"Optional phone number in E.164 format.","type":"string"}},"description":"Attributes for the user to provision inside the caller's tenant.","required":["email","name","password"]},"Created User":{"type":"object","properties":{"id":{"type":"string","example":"user_ck9z3..."},"email":{"type":"string","example":"partner-user@example.com"},"name":{"type":"string","example":"Alex Partner"},"created_at":{"type":"string","example":"2026-05-12T15:04:05.000Z","description":"ISO 8601 timestamp."}},"required":["id","email","name","created_at"],"description":"User resource returned after a successful `POST /api/v1/users`."},"Create User Response":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Created User"}},"description":"Response envelope for `POST /api/v1/users`.","required":["data"]},"Deleted User":{"type":"object","properties":{"id":{"type":"string","example":"user_ck9z3..."},"deleted_at":{"type":"string","example":"2026-05-12T15:04:05.000Z","description":"ISO 8601 timestamp at which the user was soft-deleted."}},"required":["id","deleted_at"],"description":"Tombstone returned for both the first soft-delete and any subsequent idempotent replay."},"Delete User Response":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Deleted User"}},"description":"Response envelope for `DELETE /api/v1/users/{id}`.","required":["data"]},"Plan":{"type":"object","properties":{"id":{"type":"string","example":"0c2a8c2a-1b3d-4e5f-9a0b-1234567890ab","description":"Unique plan ID."},"name":{"type":"string","example":"Growth"},"description":{"type":"string","example":"For scaling teams","nullable":true},"active":{"type":"boolean","example":true,"description":"Whether the plan is currently offered to new subscribers."},"provider":{"type":"string","enum":["stripe","agency"],"example":"stripe","description":"Billing provider behind the plan."},"currency":{"type":"string","example":"usd","description":"Lowercase ISO 4217 currency code."},"monthly_price":{"type":"number","example":9900,"description":"Monthly recurring price, in the plan's currency's minor unit (cents for USD)."},"annual_price":{"type":"number","example":99000,"description":"Annual price, in the plan's currency's minor unit."},"included_minutes":{"type":"number","example":1000,"description":"Voice minutes included with the plan each period."},"overage_rate":{"type":"number","example":0.15,"description":"Per-minute overage rate in the plan's currency's major unit."},"topup_rate":{"type":"number","example":0.15,"description":"Per-minute top-up rate in the plan's currency's major unit."},"trial_period_days":{"type":"number","example":7,"description":"Free trial length in days. `0` means no trial."},"created_at":{"type":"string","example":"2026-05-12T15:04:05.000Z","description":"ISO 8601 timestamp."},"updated_at":{"type":"string","example":"2026-05-12T15:04:05.000Z","description":"ISO 8601 timestamp."}},"required":["id","name","description","active","provider","currency","monthly_price","annual_price","included_minutes","overage_rate","topup_rate","trial_period_days","created_at","updated_at"],"description":"A subscription plan in the caller's tenant catalog. `provider` controls how billing is handled."},"List Plans Pagination":{"type":"object","properties":{"page":{"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"total":{"type":"integer","minimum":0,"maximum":9007199254740991},"pages":{"type":"integer","minimum":0,"maximum":9007199254740991},"has_more":{"type":"boolean"}},"required":["page","limit","total","pages","has_more"],"description":"Pagination block returned alongside `GET /api/v1/plans`."},"List Plans Response":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Plan"}},"pagination":{"$ref":"#/components/schemas/List Plans Pagination"}},"description":"Response envelope for `GET /api/v1/plans`.","required":["data","pagination"]},"Get Plan Response":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Plan"}},"description":"Response envelope for `GET /api/v1/plans/{id}`.","required":["data"]},"Create Subscription Request":{"type":"object","properties":{"user_id":{"type":"string","minLength":1,"example":"user_ck9z3...","description":"ID of the user to subscribe."},"plan_id":{"type":"string","minLength":1,"example":"0c2a8c2a-1b3d-4e5f-9a0b-1234567890ab","description":"ID of the plan to subscribe the user to. Must be an active plan in the caller's catalog."}},"description":"Subscription target: which user to subscribe to which plan.","required":["user_id","plan_id"]},"Subscription":{"type":"object","properties":{"id":{"type":"string","example":"sub_ck9z3...","description":"Unique subscription ID assigned by Dialora."},"user_id":{"type":"string","example":"user_ck9z3...","description":"ID of the user this subscription belongs to."},"plan_id":{"type":"string","example":"0c2a8c2a-1b3d-4e5f-9a0b-1234567890ab","description":"ID of the plan the user is on."},"status":{"type":"string","enum":["trialing","active","past_due","canceled","unpaid","paused","inactive"],"example":"active","description":"Lifecycle state of the subscription. Transient Stripe states (`incomplete`, `incomplete_expired`) and internal tombstone states are normalized to `inactive`."},"provider":{"type":"string","enum":["stripe","agency","appsumo","custom"],"example":"stripe","description":"Billing provider behind the subscription."},"provider_subscription_id":{"type":"string","example":"sub_1NABCDEFGHIJKLMN","description":"External provider's subscription identifier (e.g. Stripe `sub_*`). `null` for providers that don't expose one.","nullable":true},"cancel_at_period_end":{"type":"boolean","example":false,"description":"`true` when the subscription is set to terminate at the end of the current billing period."},"current_period_start":{"type":"string","example":"2026-05-01T00:00:00.000Z","description":"Start of the current billing period (ISO 8601)."},"current_period_end":{"type":"string","example":"2026-06-01T00:00:00.000Z","description":"End of the current billing period (ISO 8601)."},"created_at":{"type":"string","example":"2026-05-12T15:04:05.000Z","description":"ISO 8601 timestamp."},"updated_at":{"type":"string","example":"2026-05-12T15:04:05.000Z","description":"ISO 8601 timestamp."}},"required":["id","user_id","plan_id","status","provider","provider_subscription_id","cancel_at_period_end","current_period_start","current_period_end","created_at","updated_at"],"description":"A user-to-plan subscription inside the caller's tenant. `status` is the normalized public lifecycle state; transient internal states fold into `inactive`."},"Create Subscription Response":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Subscription"}},"description":"Response envelope for `POST /api/v1/subscriptions`.","required":["data"]},"List Subscriptions Pagination":{"type":"object","properties":{"page":{"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"total":{"type":"integer","minimum":0,"maximum":9007199254740991},"pages":{"type":"integer","minimum":0,"maximum":9007199254740991},"has_more":{"type":"boolean"}},"required":["page","limit","total","pages","has_more"],"description":"Pagination block returned alongside `GET /api/v1/subscriptions`."},"List Subscriptions Response":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}},"pagination":{"$ref":"#/components/schemas/List Subscriptions Pagination"}},"description":"Response envelope for `GET /api/v1/subscriptions`.","required":["data","pagination"]},"Get Subscription Response":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Subscription"}},"description":"Response envelope for `GET /api/v1/subscriptions/{id}`.","required":["data"]},"Change Subscription Plan Request":{"type":"object","properties":{"plan_id":{"type":"string","minLength":1,"example":"0c2a8c2a-1b3d-4e5f-9a0b-1234567890ab","description":"Target plan ID in the caller's catalog. Must be an active plan with the same `provider` and `currency` as the subscription's current plan."}},"description":"Body for `PATCH /api/v1/subscriptions/{id}/plan` — the target plan to switch the subscription to.","required":["plan_id"]},"Change Subscription Plan Response":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Subscription"}},"description":"Response envelope for `PATCH /api/v1/subscriptions/{id}/plan`. The `data` payload is the same shape as the rest of the subscription endpoints — the action (`upgrade` / `downgrade`) is determined server-side from the new plan's monthly price relative to the current plan; partners can re-derive it by comparing the returned `plan_id` against their request.","required":["data"]}}},"x-tagGroups":[{"name":"Agency","tags":["Agency / Plans","Agency / Subscriptions","Agency / Users"]}]}