diff --git a/.claude/rules/backend/api-keys.md b/.claude/rules/backend/api-keys.md new file mode 100644 index 0000000000..981580e79f --- /dev/null +++ b/.claude/rules/backend/api-keys.md @@ -0,0 +1,22 @@ +--- +paths: + - "backend/app/**/ApiKey*/**" + - "backend/app/**/*ApiKey*.php" + - "backend/app/Services/Domain/Auth/AuthUserService.php" + - "backend/app/Http/Middleware/**" + - "backend/routes/api.php" + - "backend/config/sanctum.php" + - "backend/tests/Feature/Http/ApiKeys/**" + - "frontend/src/components/routes/settings/ApiKeys/**" + - "frontend/src/components/routes/settings/ApiReference/**" + - "e2e/tests/account/api-keys.spec.ts" +--- + +# API keys + +- API keys are Sanctum personal access tokens owned by a user and scoped to one account (`personal_access_tokens.account_id`). A key acts as that member: their role, grants and permission overrides apply, and it can never do more. Read-only keys carry only the `read` ability; `EnforceAccountUserAccess` blocks their writes with `API_KEY_READ_ONLY` (exports and previews marked `#[AllowedForReadOnlyUsers]` stay open). +- Signed-in routes live in two groups. `auth:api,sanctum` accepts keys; `auth:api` is session-only: auth, profile and sign-in security, team, account settings and security, payouts (Stripe Connect, terminal readers, VAT) and API key management. A new route that changes who can sign in, team membership, money destinations or account security goes in the session-only group; `ApiKeyRouteExposureTest` snapshots that group, and no `MANAGE_TEAM`, `MANAGE_ACCOUNT` or `MANAGE_PAYMENTS` action may accept keys. +- Never read JWT claims with `Auth::payload()` on a route keys can reach: it throws under the Sanctum guard. Use `AuthUserService` (`getAuthenticatedAccountId`, `isImpersonating`, `getSessionTokenId`, `logout`). The access cache key for a key request is `api-key:{id}`. +- A key works only while its owner is an ACTIVE member of its account (Sanctum `authenticateAccessTokensUsing` in `AppServiceProvider`); deactivating a member deletes their keys, and so does every `UserSessionRevocationService` call (password reset, SSO link changes, enforcement), because a key must never outlive a credential reset. Revocations driven by one account's SSO settings pass that account id, so only its keys go. A voluntary password change or turning 2FA off keeps keys on purpose (as GitHub and Stripe do); the key-created email tells the owner to revoke a key they didn't create. The account's 2FA requirement and pending deletion also block keys. +- Only the SHA-256 hash and the last four characters are stored; the plain key is returned once, on create. Creating a key needs a verified email and `IdentityConfirmationService`, is refused while impersonating, and is capped at `MAX_ACTIVE_KEYS_PER_MEMBER`. Key names are restricted to a markdown-safe character set because they appear in emails. +- The in-app API reference (`/settings/api-keys/reference`) renders the committed `resources/api-reference/openapi.json` with Scalar, themed through `scalarTheme.ts`. Keep Scalar's AI agent, MCP and telemetry switched off, and load it client-only (it touches `window` on import). diff --git a/.claude/rules/backend/openapi.md b/.claude/rules/backend/openapi.md index 5abd1f6afa..015bb377c0 100644 --- a/.claude/rules/backend/openapi.md +++ b/.claude/rules/backend/openapi.md @@ -1,5 +1,6 @@ --- paths: + - "backend/resources/api-reference/**" - "backend/app/OpenApi/**" - "backend/app/Providers/ScrambleServiceProvider.php" - "backend/config/scramble.php" @@ -14,3 +15,6 @@ paths: - Status/type fields in the Order/Event/Attendee/Product resources carry `/** @var 'A'|'B' */` literal-union annotations that render as enums; update them when adding enum cases. - `CreateOrderRequest`/`CompleteOrderRequest` return their real rules only when a route is bound and a static documentation shape otherwise; keep the two in sync when changing checkout validation. - Adding a `DomainEventType` case means updating the webhook payload table in `config/scramble.php`. +- `backend/resources/api-reference/openapi.json` is the generated in-app API reference (Settings → API keys → API reference): Scramble output minus session-only operations and `/auth/*`, without `servers` and `info.version` (added when served). Regenerate it with `php artisan api-reference:export`; `IntegrationApiReferenceTest` fails when it drifts. Never edit it by hand. +- The docs intro in `config/scramble.php` promises that endpoints API keys can call stay backward compatible (see "Public API compatibility" in AGENTS.md) and lists the error codes. Update its error table when adding an `error_code`. Session-only operations are marked "Not available to API keys" automatically by `ScrambleServiceProvider`. +- Admin routes and the licensing endpoints (`Enterprise\Licensing` actions) are excluded from all generated docs in `ScrambleServiceProvider`. diff --git a/.claude/rules/backend/sso.md b/.claude/rules/backend/sso.md index 20e37dd79a..18f3811425 100644 --- a/.claude/rules/backend/sso.md +++ b/.claude/rules/backend/sso.md @@ -31,5 +31,7 @@ paths: - Every outbound request to a provider goes through `SsrfGuard` (https, public addresses only, pinned with `CURLOPT_RESOLVE`) unless `SSO_ALLOW_PRIVATE_ISSUERS` is set; never set it on multi-tenant installs. - Domain status is derived from `verified_at` and `failing_since` (suspended seven days after the daily check starts failing); one account per verified domain, conflicts are resolved with `sso:verify-domain`. - Enforcement is `account_sso_connections.enforce_sso` plus the `account_users.sso_enforced` snapshot (set at enable and when someone joins, cleared only by an admin's exempt toggle). It's checked in `LoginService::completeLogin` and `EnsureTokenNotRevoked`; tokens must carry the account's `sso_connection_id`, and a token issued through a connection dies when that connection is turned off or deleted. A member the connection can't sign in (owner of any account or superadmin, with no identity on it) is not enforced, so they keep their password; the preview reports them as `sso_available: false`. A verified domain can't be removed while enforcing. The owner is never enforced: their password sign-in always asks for 2FA, emails every admin (the owner gets their own copy), and neither they nor support can turn their 2FA off while enforcing. +- Forgot-password silently sends nothing when `isPasswordResetAllowed` is false; `/users/me` exposes the same answer as `password_reset_allowed` so Security settings doesn't offer a password link that never arrives. +- Domain capture: while a connection enforces, nobody can sign up with an email on its usable domains. `SsoEnforcementService::assertSignupAllowed` throws `SsoRequiredException` from `CreateAccountHandler` (password and social signup) and before a social signup ticket is issued; the frontend sends them to SSO discovery. Invitations to other accounts are not captured. - Tokens issued through a connection use its session length and can't be refreshed (`SSO_SESSION_EXPIRED`); the frontend re-authenticates with `prompt=none`. - Configuration endpoints need `Permission::MANAGE_ACCOUNT` and `FeatureFlag::ENTERPRISE_SSO` (SaaS only); sign-in through an existing connection is never flag-gated. Owners are emailed when someone else changes the provider or enforcement. diff --git a/.claude/rules/backend/team-access.md b/.claude/rules/backend/team-access.md index 47da245760..2865318c7f 100644 --- a/.claude/rules/backend/team-access.md +++ b/.claude/rules/backend/team-access.md @@ -22,5 +22,5 @@ paths: - Treat any value that unlocks a public page as a credential. Staff responses return `orders.short_id` only to members with EDIT + VIEW_FINANCIALS; check-in list links are shareable with door staff on purpose. - Deliberate, don't "fix": VIEW_FINANCIALS hides money in the dashboard but doesn't stop an EDIT member from configuring outbound channels (organizer email, email templates, analytics pixels) or running the door, which shows door sales. - Public "same account" checks (draft previews, test orders, check-in staff view) use `AccountUserAccessService::currentMemberCanAccessEvent/Organizer`, never a raw account id comparison. -- `AccountUserAccessService` caches access per request keyed by the JWT string; never key it on decoded payload claims (resources call `isPermitted()` hundreds of times per list). +- `AccountUserAccessService` caches access per request keyed by `AuthUserService::getAuthenticatedTokenKey()` (the JWT string, or `api-key:{id}` for API keys); never key it on decoded payload claims (resources call `isPermitted()` hundreds of times per list). - Frontend: 403s carrying `ACCESS_RESTRICTED`, `PERMISSION_REQUIRED` or `READ_ONLY_ROLE` must never send the user to login. diff --git a/AGENTS.md b/AGENTS.md index 5cfa6e8532..ebf78ceb9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,7 @@ Backend commands run in the `backend` container from `docker/development` (start ```bash docker compose -f docker-compose.dev.yml exec backend php artisan migrate docker compose -f docker-compose.dev.yml exec backend php artisan generate-domain-objects +docker compose -f docker-compose.dev.yml exec backend php artisan api-reference:export docker compose -f docker-compose.dev.yml exec -T backend php -d memory_limit=1G vendor/bin/phpunit --no-coverage --testsuite=Unit docker compose -f docker-compose.dev.yml exec -T backend php -d memory_limit=1G vendor/bin/phpunit --no-coverage --filter=TestName docker compose -f docker-compose.dev.yml exec backend ./vendor/bin/pint --test @@ -31,7 +32,7 @@ yarn messages:extract # then add translations for every language (translations Schema changes: `php artisan make:migration`, `php artisan migrate`, then `php artisan generate-domain-objects`. Domain objects are generated; never edit them by hand. -Before finishing: `npx tsc --noEmit` (no new errors in files you changed), `pint --test`, the Unit suite, and the Feature suite before opening a PR (CI runs both). For a changed endpoint, also smoke-test it against the dev stack (api-smoke-test skill). +Before finishing: `npx tsc --noEmit` (no new errors in files you changed), `pint --test`, the Unit suite, and the Feature suite before opening a PR (CI runs both). For a changed endpoint, also run `php artisan api-reference:export` (the committed in-app reference must match the code; a Feature test enforces it) and smoke-test it against the dev stack (api-smoke-test skill). ## Code standards @@ -52,6 +53,10 @@ Before finishing: `npx tsc --noEmit` (no new errors in files you changed), `pint - Feature flags gate only in SaaS mode: `FeatureFlag` enum + a migration inserting its row (`enabled_by_default = true` for a live feature) + frontend label/constant; check with `FeatureFlagService::assertEnabled` / `useIsFeatureEnabled`. - Laravel event auto-discovery is load-bearing: most listeners are registered only by it, so never turn off `shouldDiscoverEvents()`. +## Public API compatibility + +Integrations call the API with API keys, so endpoints in the `auth:api,sanctum` route group, everything under `/public/*`, webhook payloads and `error_code` values are a public contract. Adding endpoints, optional request fields and response fields is fine. Removing or renaming a route, field or error code, changing a field's type, format or meaning, making an optional field required, tightening validation, or moving a route into the session-only group is a breaking change: avoid it, and when it's unavoidable, tell the user before making it and call it out in the PR description and release notes. Keep the resources' OpenAPI annotations accurate (`.claude/rules/backend/openapi.md`); review the diff of `backend/resources/api-reference/openapi.json` for anything removed or changed. + ## Data rules - Event dates come from `event_occurrences`. The `events.start_date`/`end_date` columns exist only for a v1 rollback: never read or write them. @@ -98,6 +103,7 @@ Feature rules live in `.claude/rules/`; Claude Code loads each file automaticall | Lifecycle emails | `.claude/rules/backend/lifecycle-emails.md` | | Two-factor authentication | `.claude/rules/backend/two-factor.md` | | Single sign-on (OIDC) | `.claude/rules/backend/sso.md` | +| API keys | `.claude/rules/backend/api-keys.md` | | Abuse & spam | `.claude/rules/backend/abuse-and-spam.md` | | Account deletion | `.claude/rules/backend/account-deletion.md` | | OpenAPI docs | `.claude/rules/backend/openapi.md` | diff --git a/backend/app/Console/Commands/ExportApiReferenceCommand.php b/backend/app/Console/Commands/ExportApiReferenceCommand.php new file mode 100644 index 0000000000..c5981b5a1c --- /dev/null +++ b/backend/app/Console/Commands/ExportApiReferenceCommand.php @@ -0,0 +1,38 @@ +generate(); + $committed = is_file($reference->path()) ? file_get_contents($reference->path()) : null; + + if ($this->option('check')) { + if ($generated === $committed) { + $this->info('The API reference is up to date.'); + + return self::SUCCESS; + } + + $this->error('The API reference is out of date. Run `php artisan api-reference:export` and commit resources/api-reference/openapi.json.'); + + return self::FAILURE; + } + + file_put_contents($reference->path(), $generated); + $this->info('Wrote '.$reference->path()); + + return self::SUCCESS; + } +} diff --git a/backend/app/Console/Kernel.php b/backend/app/Console/Kernel.php index c219280d9d..a47082b2de 100644 --- a/backend/app/Console/Kernel.php +++ b/backend/app/Console/Kernel.php @@ -6,6 +6,7 @@ use HiEvents\Enterprise\Licensing\Console\IssueLicenceKeyCommand; use HiEvents\Jobs\Abuse\ReportSuspiciousActivityJob; use HiEvents\Jobs\Account\ProcessScheduledAccountDeletionsJob; +use HiEvents\Jobs\ApiKey\SendApiKeyExpiryRemindersJob; use HiEvents\Jobs\Auth\RecheckSsoDomainsJob; use HiEvents\Jobs\Lifecycle\SendLifecycleNudgesJob; use HiEvents\Jobs\Message\SendScheduledMessagesJob; @@ -29,6 +30,8 @@ protected function schedule(Schedule $schedule): void $schedule->job(new ProcessExpiredWaitlistOffersJob)->everyMinute()->withoutOverlapping(); $schedule->job(new ProcessScheduledAccountDeletionsJob)->hourly()->withoutOverlapping(); $schedule->job(new RecheckSsoDomainsJob)->daily()->withoutOverlapping(); + $schedule->job(new SendApiKeyExpiryRemindersJob)->dailyAt('09:00')->withoutOverlapping(); + $schedule->command('sanctum:prune-expired', ['--hours' => 24 * 30])->daily(); $schedule->job(new ReportSuspiciousActivityJob)->everyFiveMinutes()->withoutOverlapping()->when(fn () => (bool) config('app.saas_mode_enabled')); $schedule->job(new SendLifecycleNudgesJob)->hourly()->withoutOverlapping() ->when(static fn (): bool => app(LifecycleEmailSender::class)->isConfigured()); diff --git a/backend/app/DomainObjects/Enums/ApiKeyAccess.php b/backend/app/DomainObjects/Enums/ApiKeyAccess.php new file mode 100644 index 0000000000..314b529cb2 --- /dev/null +++ b/backend/app/DomainObjects/Enums/ApiKeyAccess.php @@ -0,0 +1,34 @@ + [self::READ_ABILITY], + self::READ_WRITE => [self::READ_ABILITY, self::WRITE_ABILITY], + }; + } + + /** + * @param string[] $abilities + */ + public static function fromAbilities(array $abilities): self + { + return in_array(self::WRITE_ABILITY, $abilities, true) ? self::READ_WRITE : self::READ_ONLY; + } +} diff --git a/backend/app/DomainObjects/Generated/PersonalAccessTokenDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/PersonalAccessTokenDomainObjectAbstract.php index 4a58970723..748a48fc08 100644 --- a/backend/app/DomainObjects/Generated/PersonalAccessTokenDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/PersonalAccessTokenDomainObjectAbstract.php @@ -11,6 +11,7 @@ abstract class PersonalAccessTokenDomainObjectAbstract extends \HiEvents\DomainO final public const SINGULAR_NAME = 'personal_access_token'; final public const PLURAL_NAME = 'personal_access_tokens'; final public const ID = 'id'; + final public const ACCOUNT_ID = 'account_id'; final public const TOKENABLE_TYPE = 'tokenable_type'; final public const TOKENABLE_ID = 'tokenable_id'; final public const NAME = 'name'; @@ -20,8 +21,11 @@ abstract class PersonalAccessTokenDomainObjectAbstract extends \HiEvents\DomainO final public const EXPIRES_AT = 'expires_at'; final public const CREATED_AT = 'created_at'; final public const UPDATED_AT = 'updated_at'; + final public const TOKEN_HINT = 'token_hint'; + final public const EXPIRY_REMINDER_SENT_AT = 'expiry_reminder_sent_at'; protected int $id; + protected int $account_id; protected string $tokenable_type; protected int $tokenable_id; protected string $name; @@ -31,11 +35,14 @@ abstract class PersonalAccessTokenDomainObjectAbstract extends \HiEvents\DomainO protected ?string $expires_at = null; protected ?string $created_at = null; protected ?string $updated_at = null; + protected string $token_hint; + protected ?string $expiry_reminder_sent_at = null; public function toArray(): array { return [ 'id' => $this->id ?? null, + 'account_id' => $this->account_id ?? null, 'tokenable_type' => $this->tokenable_type ?? null, 'tokenable_id' => $this->tokenable_id ?? null, 'name' => $this->name ?? null, @@ -45,6 +52,8 @@ public function toArray(): array 'expires_at' => $this->expires_at ?? null, 'created_at' => $this->created_at ?? null, 'updated_at' => $this->updated_at ?? null, + 'token_hint' => $this->token_hint ?? null, + 'expiry_reminder_sent_at' => $this->expiry_reminder_sent_at ?? null, ]; } @@ -59,6 +68,17 @@ public function getId(): int return $this->id; } + public function setAccountId(int $account_id): self + { + $this->account_id = $account_id; + return $this; + } + + public function getAccountId(): int + { + return $this->account_id; + } + public function setTokenableType(string $tokenable_type): self { $this->tokenable_type = $tokenable_type; @@ -157,4 +177,26 @@ public function getUpdatedAt(): ?string { return $this->updated_at; } + + public function setTokenHint(string $token_hint): self + { + $this->token_hint = $token_hint; + return $this; + } + + public function getTokenHint(): string + { + return $this->token_hint; + } + + public function setExpiryReminderSentAt(?string $expiry_reminder_sent_at): self + { + $this->expiry_reminder_sent_at = $expiry_reminder_sent_at; + return $this; + } + + public function getExpiryReminderSentAt(): ?string + { + return $this->expiry_reminder_sent_at; + } } diff --git a/backend/app/DomainObjects/PersonalAccessTokenDomainObject.php b/backend/app/DomainObjects/PersonalAccessTokenDomainObject.php index 5816dfe6cd..d1b431cdff 100644 --- a/backend/app/DomainObjects/PersonalAccessTokenDomainObject.php +++ b/backend/app/DomainObjects/PersonalAccessTokenDomainObject.php @@ -2,4 +2,26 @@ namespace HiEvents\DomainObjects; -class PersonalAccessTokenDomainObject extends Generated\PersonalAccessTokenDomainObjectAbstract {} +use HiEvents\DomainObjects\Enums\ApiKeyAccess; + +class PersonalAccessTokenDomainObject extends Generated\PersonalAccessTokenDomainObjectAbstract +{ + private ?UserDomainObject $user = null; + + public function getAccess(): ApiKeyAccess + { + return ApiKeyAccess::fromAbilities(json_decode($this->getAbilities() ?? '[]', true) ?: []); + } + + public function setUser(?UserDomainObject $user): self + { + $this->user = $user; + + return $this; + } + + public function getUser(): ?UserDomainObject + { + return $this->user; + } +} diff --git a/backend/app/Exceptions/ApiKeyNotAllowedException.php b/backend/app/Exceptions/ApiKeyNotAllowedException.php new file mode 100644 index 0000000000..94dfdce8a9 --- /dev/null +++ b/backend/app/Exceptions/ApiKeyNotAllowedException.php @@ -0,0 +1,15 @@ +user(); if ($user) { $ip = request()->ip(); - $isImpersonating = (bool) auth()->payload()->get('is_impersonating', false); - $impersonatorId = $isImpersonating ? auth()->payload()->get('impersonator_id') : null; + $authUserService = app(AuthUserService::class); + $isImpersonating = $authUserService->isImpersonating(); + $impersonatorId = $authUserService->getImpersonatorId(); Sentry::configureScope(function (Scope $scope) use ($user, $ip, $isImpersonating, $impersonatorId): void { $scope->setUser([ diff --git a/backend/app/Helper/Url.php b/backend/app/Helper/Url.php index 722412fe53..d83e9b5d13 100644 --- a/backend/app/Helper/Url.php +++ b/backend/app/Helper/Url.php @@ -32,6 +32,8 @@ class Url public const PROFILE_SECURITY = 'app.frontend_urls.profile_security'; + public const API_KEYS = 'app.frontend_urls.api_keys'; + public const ACCOUNT_USERS = 'app.frontend_urls.account_users'; public const ACCOUNT_SETTINGS = 'app.frontend_urls.account_settings'; diff --git a/backend/app/Http/Actions/ApiKeys/CreateApiKeyAction.php b/backend/app/Http/Actions/ApiKeys/CreateApiKeyAction.php new file mode 100644 index 0000000000..04508b7544 --- /dev/null +++ b/backend/app/Http/Actions/ApiKeys/CreateApiKeyAction.php @@ -0,0 +1,55 @@ +isImpersonating()) { + return $this->impersonationForbiddenResponse(__('API keys cannot be created while impersonating a user.')); + } + + try { + $created = $this->handler->handle(new CreateApiKeyDTO( + userId: $this->getAuthenticatedUser()->getId(), + accountId: $this->getAuthenticatedAccountId(), + name: trim($request->validated('name')), + access: ApiKeyAccess::from($request->validated('access')), + expiresInDays: $request->validated('expires_in_days'), + canEdit: $this->getAuthenticatedAccess()->can(Permission::EDIT), + password: $request->validated('password'), + reauthProof: $request->validated('reauth_proof'), + tokenId: $this->getAuthTokenId(), + )); + } catch (IdentityConfirmationFailedException $exception) { + throw ValidationException::withMessages(['password' => $exception->getMessage()]); + } catch (ResourceConflictException $exception) { + return $this->errorResponse($exception->getMessage(), ResponseCodes::HTTP_CONFLICT); + } + + return $this->resourceResponse(CreatedApiKeyResource::class, $created, ResponseCodes::HTTP_CREATED); + } +} diff --git a/backend/app/Http/Actions/ApiKeys/DeleteApiKeyAction.php b/backend/app/Http/Actions/ApiKeys/DeleteApiKeyAction.php new file mode 100644 index 0000000000..8d4041af30 --- /dev/null +++ b/backend/app/Http/Actions/ApiKeys/DeleteApiKeyAction.php @@ -0,0 +1,34 @@ +handler->handle(new DeleteApiKeyDTO( + apiKeyId: $apiKeyId, + accountId: $this->getAuthenticatedAccountId(), + userId: $this->getAuthenticatedUser()->getId(), + canManageTeam: $this->getAuthenticatedAccess()->can(Permission::MANAGE_TEAM), + )); + + return $this->deletedResponse(); + } +} diff --git a/backend/app/Http/Actions/ApiKeys/GetApiKeysAction.php b/backend/app/Http/Actions/ApiKeys/GetApiKeysAction.php new file mode 100644 index 0000000000..edc8e1ad11 --- /dev/null +++ b/backend/app/Http/Actions/ApiKeys/GetApiKeysAction.php @@ -0,0 +1,33 @@ +resourceResponse( + ApiKeyResource::class, + $this->handler->handle( + accountId: $this->getAuthenticatedAccountId(), + userId: $this->getAuthenticatedUser()->getId(), + includeTeamKeys: $this->getAuthenticatedAccess()->can(Permission::MANAGE_TEAM), + ), + meta: [ + 'expiry_options_in_days' => ApiKeyService::EXPIRY_OPTIONS_IN_DAYS, + 'max_active_keys' => ApiKeyService::MAX_ACTIVE_KEYS_PER_MEMBER, + ], + ); + } +} diff --git a/backend/app/Http/Actions/ApiReference/GetApiReferenceAction.php b/backend/app/Http/Actions/ApiReference/GetApiReferenceAction.php new file mode 100644 index 0000000000..d277ca389b --- /dev/null +++ b/backend/app/Http/Actions/ApiReference/GetApiReferenceAction.php @@ -0,0 +1,19 @@ +jsonResponse($this->reference->published()); + } +} diff --git a/backend/app/Http/Actions/BaseAction.php b/backend/app/Http/Actions/BaseAction.php index 96a59da8b4..244b1a6f25 100644 --- a/backend/app/Http/Actions/BaseAction.php +++ b/backend/app/Http/Actions/BaseAction.php @@ -29,7 +29,6 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Response; -use PHPOpenSourceSaver\JWTAuth\Exceptions\JWTException; abstract class BaseAction extends Controller { @@ -252,22 +251,12 @@ protected function isUserAuthenticated(): bool protected function getAuthTokenId(): ?string { - try { - $tokenId = Auth::payload()->get('jti'); - } catch (JWTException) { - return null; - } - - return is_string($tokenId) ? $tokenId : null; + return app(AuthUserService::class)->getSessionTokenId(); } protected function isImpersonating(): bool { - try { - return (bool) Auth::payload()->get('is_impersonating', false); - } catch (JWTException) { - return false; - } + return app(AuthUserService::class)->isImpersonating(); } protected function impersonationForbiddenResponse(string $message): JsonResponse diff --git a/backend/app/Http/Middleware/Authenticate.php b/backend/app/Http/Middleware/Authenticate.php index 368b8a4309..339d91bab6 100644 --- a/backend/app/Http/Middleware/Authenticate.php +++ b/backend/app/Http/Middleware/Authenticate.php @@ -2,11 +2,30 @@ namespace HiEvents\Http\Middleware; +use HiEvents\Exceptions\ApiKeyNotAllowedException; +use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Middleware\Authenticate as Middleware; use Illuminate\Http\Request; class Authenticate extends Middleware { + /** + * @throws AuthenticationException + * @throws ApiKeyNotAllowedException + */ + protected function unauthenticated($request, array $guards) + { + if ($this->carriesApiKey($request)) { + if (! in_array('sanctum', $guards, true)) { + throw new ApiKeyNotAllowedException; + } + + throw new AuthenticationException(__('This API key is invalid, has expired or has been revoked.'), $guards); + } + + parent::unauthenticated($request, $guards); + } + /** * Get the path the user should be redirected to when they are not authenticated. */ @@ -14,4 +33,12 @@ protected function redirectTo(Request $request): ?string { return $request->expectsJson() ? null : route('auth.login'); } + + private function carriesApiKey(Request $request): bool + { + $prefix = (string) config('sanctum.token_prefix'); + $token = (string) $request->bearerToken(); + + return $prefix !== '' && (str_starts_with($token, $prefix) || str_contains($token, '|'.$prefix)); + } } diff --git a/backend/app/Http/Middleware/EnforceAccountUserAccess.php b/backend/app/Http/Middleware/EnforceAccountUserAccess.php index 5c8bc6e3b4..ca074a84a6 100644 --- a/backend/app/Http/Middleware/EnforceAccountUserAccess.php +++ b/backend/app/Http/Middleware/EnforceAccountUserAccess.php @@ -5,6 +5,7 @@ use Closure; use HiEvents\DomainObjects\Enums\Permission; use HiEvents\DomainObjects\Status\UserStatus; +use HiEvents\Exceptions\ApiKeyReadOnlyException; use HiEvents\Exceptions\PermissionRequiredException; use HiEvents\Exceptions\ReadOnlyAccessException; use HiEvents\Exceptions\UnauthorizedException; @@ -34,12 +35,16 @@ public function handle(Request $request, Closure $next) $controller = $request->route()?->getControllerClass(); $accountUser = $this->authUserService->getUser()?->getCurrentAccountUser(); + if ($accountUser === null && $this->authUserService->isApiKeyRequest()) { + throw new UnauthorizedException; + } + if ($accountUser === null || $controller === StopImpersonationAction::class) { return $next($request); } if ($accountUser->getStatus() !== UserStatus::ACTIVE->name) { - Auth::logout(); + $this->authUserService->logout(); throw new UnauthorizedException(__('Your account is not active.')); } @@ -56,6 +61,10 @@ public function handle(Request $request, Closure $next) throw new ReadOnlyAccessException; } + if ($this->isBlockedForReadOnlyApiKey($request, $controller)) { + throw new ApiKeyReadOnlyException; + } + return $next($request); } @@ -75,13 +84,23 @@ private function requiredPermissions(?string $controller): array private function isBlockedForReadOnlyUser(Request $request, AccountUserAccess $access, ?string $controller): bool { - if ($access->can(Permission::EDIT) || $request->isMethodSafe()) { - return false; - } + return ! $access->can(Permission::EDIT) + && ! $request->isMethodSafe() + && ! $this->isAllowedForReadOnlyUsers($controller); + } - return $controller === null - || ! class_exists($controller) - || (new ReflectionClass($controller))->getAttributes(AllowedForReadOnlyUsers::class) === []; + private function isBlockedForReadOnlyApiKey(Request $request, ?string $controller): bool + { + return ! $this->authUserService->apiKeyCanWrite() + && ! $request->isMethodSafe() + && ! $this->isAllowedForReadOnlyUsers($controller); + } + + private function isAllowedForReadOnlyUsers(?string $controller): bool + { + return $controller !== null + && class_exists($controller) + && (new ReflectionClass($controller))->getAttributes(AllowedForReadOnlyUsers::class) !== []; } private function routeRequiresAuthentication(Request $request): bool diff --git a/backend/app/Http/Middleware/EnsureTokenNotRevoked.php b/backend/app/Http/Middleware/EnsureTokenNotRevoked.php index 541e18c89f..13c1bc8d73 100644 --- a/backend/app/Http/Middleware/EnsureTokenNotRevoked.php +++ b/backend/app/Http/Middleware/EnsureTokenNotRevoked.php @@ -5,6 +5,7 @@ use Closure; use HiEvents\DomainObjects\Enums\SsoErrorCode; use HiEvents\Models\User; +use HiEvents\Services\Domain\Auth\AuthUserService; use HiEvents\Services\Domain\Auth\Sso\Enterprise\SsoEnforcementService; use HiEvents\Services\Domain\Auth\UserSessionRevocationService; use Illuminate\Http\Request; @@ -20,11 +21,14 @@ class EnsureTokenNotRevoked public function __construct( private readonly UserSessionRevocationService $revocationService, private readonly SsoEnforcementService $ssoEnforcementService, + private readonly AuthUserService $authUserService, ) {} public function handle(Request $request, Closure $next) { - $errorCode = $this->carriesToken($request) && Auth::check() ? $this->rejectionCode() : null; + $errorCode = $this->carriesToken($request) && Auth::check() && ! $this->authUserService->isApiKeyRequest() + ? $this->rejectionCode() + : null; if ($errorCode === null) { return $next($request); diff --git a/backend/app/Http/Middleware/EnsureTwoFactorEnrolled.php b/backend/app/Http/Middleware/EnsureTwoFactorEnrolled.php index 90056befa8..660567fdbf 100644 --- a/backend/app/Http/Middleware/EnsureTwoFactorEnrolled.php +++ b/backend/app/Http/Middleware/EnsureTwoFactorEnrolled.php @@ -8,7 +8,6 @@ use HiEvents\Services\Domain\Auth\TwoFactor\TwoFactorRequirementService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use PHPOpenSourceSaver\JWTAuth\Exceptions\JWTException; use Symfony\Component\HttpFoundation\Response; class EnsureTwoFactorEnrolled @@ -29,7 +28,7 @@ public function handle(Request $request, Closure $next) /** @var User $user */ $user = Auth::user(); - if ($user->two_factor_confirmed_at !== null || $this->isImpersonating() || $this->isAllowedBeforeEnrolment($request)) { + if ($user->two_factor_confirmed_at !== null || $this->authUserService->isImpersonating() || $this->isAllowedBeforeEnrolment($request)) { return $next($request); } @@ -51,15 +50,6 @@ private function routeRequiresAuthentication(Request $request): bool ->contains(fn ($middleware) => is_string($middleware) && str_starts_with($middleware, 'auth:')); } - private function isImpersonating(): bool - { - try { - return (bool) Auth::payload()->get('is_impersonating', false); - } catch (JWTException) { - return false; - } - } - private function isAllowedBeforeEnrolment(Request $request): bool { $path = trim($request->path(), '/'); diff --git a/backend/app/Http/Middleware/LogImpersonationMiddleware.php b/backend/app/Http/Middleware/LogImpersonationMiddleware.php index e19d5c0d6a..d7f4c454e9 100644 --- a/backend/app/Http/Middleware/LogImpersonationMiddleware.php +++ b/backend/app/Http/Middleware/LogImpersonationMiddleware.php @@ -3,7 +3,7 @@ namespace HiEvents\Http\Middleware; use Closure; -use Exception; +use HiEvents\Services\Domain\Auth\AuthUserService; use Illuminate\Auth\AuthManager; use Illuminate\Http\Request; use Psr\Log\LoggerInterface; @@ -13,27 +13,22 @@ class LogImpersonationMiddleware public function __construct( private readonly LoggerInterface $logger, private readonly AuthManager $authManager, + private readonly AuthUserService $authUserService, ) {} public function handle(Request $request, Closure $next) { $mutateMethods = ['POST', 'PUT', 'PATCH', 'DELETE']; - $isImpersonating = false; - try { - $isImpersonating = (bool) $this->authManager->payload()->get('is_impersonating', false); - } catch (Exception) { - // Not authenticated or no JWT token - } - - if ($this->authManager->check() - && $isImpersonating + if ($this->authUserService->isImpersonating() && in_array($request->method(), $mutateMethods, true) ) { - $this->logger->info('Impersonation action by user ID '.$this->authManager->payload()->get('impersonator_id'), [ - 'impersonator_id' => $this->authManager->payload()->get('impersonator_id'), + $impersonatorId = $this->authUserService->getImpersonatorId(); + + $this->logger->info('Impersonation action by user ID '.$impersonatorId, [ + 'impersonator_id' => $impersonatorId, 'impersonated_user_id' => $this->authManager->user()->id, - 'account_id' => $this->authManager->payload()->get('account_id'), + 'account_id' => $this->authUserService->getAuthenticatedAccountId(), 'method' => $request->method(), 'url' => $request->fullUrl(), 'ip' => $request->ip(), diff --git a/backend/app/Http/Middleware/SetAccountContext.php b/backend/app/Http/Middleware/SetAccountContext.php index 042b58a115..98da3df712 100644 --- a/backend/app/Http/Middleware/SetAccountContext.php +++ b/backend/app/Http/Middleware/SetAccountContext.php @@ -4,18 +4,18 @@ use Closure; use HiEvents\Models\User; -use Illuminate\Support\Facades\Auth; +use HiEvents\Services\Domain\Auth\AuthUserService; class SetAccountContext { + public function __construct( + private readonly AuthUserService $authUserService, + ) {} + public function handle($request, Closure $next) { - if (Auth::check()) { - $accountId = Auth::payload()->get('account_id'); - - if ($accountId) { - User::setCurrentAccountId($accountId); - } + if ($accountId = $this->authUserService->getAuthenticatedAccountId()) { + User::setCurrentAccountId($accountId); } return $next($request); diff --git a/backend/app/Http/Request/ApiKey/CreateApiKeyRequest.php b/backend/app/Http/Request/ApiKey/CreateApiKeyRequest.php new file mode 100644 index 0000000000..1573e99deb --- /dev/null +++ b/backend/app/Http/Request/ApiKey/CreateApiKeyRequest.php @@ -0,0 +1,32 @@ + ['required', 'string', 'max:60', 'regex:'.self::NAME_PATTERN], + 'access' => ['required', Rule::enum(ApiKeyAccess::class)], + 'expires_in_days' => ['present', 'nullable', 'integer', Rule::in(ApiKeyService::EXPIRY_OPTIONS_IN_DAYS)], + 'password' => ['nullable', 'string', 'max:255'], + 'reauth_proof' => ['nullable', 'string', 'max:128'], + ]; + } + + public function messages(): array + { + return [ + 'name.regex' => __('Use letters, numbers, spaces and basic punctuation in the name.'), + 'expires_in_days.in' => __('Choose one of the available expiry options.'), + ]; + } +} diff --git a/backend/app/Http/Request/Organizer/Settings/PartialUpdateOrganizerSettingsRequest.php b/backend/app/Http/Request/Organizer/Settings/PartialUpdateOrganizerSettingsRequest.php index a55d493d5a..8c8376f16a 100644 --- a/backend/app/Http/Request/Organizer/Settings/PartialUpdateOrganizerSettingsRequest.php +++ b/backend/app/Http/Request/Organizer/Settings/PartialUpdateOrganizerSettingsRequest.php @@ -12,6 +12,7 @@ use HiEvents\Http\Request\BaseRequest; use HiEvents\Repository\Interfaces\OrganizerSettingsRepositoryInterface; use HiEvents\Services\Domain\Auth\Access\AccountUserAccessService; +use HiEvents\Services\Domain\Auth\AuthUserService; use HiEvents\Validators\Rules\HomepageSectionRules; use HiEvents\Validators\Rules\RulesHelper; use Illuminate\Validation\Rule; @@ -43,6 +44,18 @@ function ($validator) { continue; } + if ($provider === TrackingPixelProvider::GOOGLE_TAG_MANAGER + && app(AuthUserService::class)->isApiKeyRequest() + && ! in_array($pixelId, $this->existingTagManagerIds(), true) + ) { + $validator->errors()->add( + "tracking_pixels.{$index}.provider", + __('Google Tag Manager containers can only be added from the dashboard, not with an API key.') + ); + + continue; + } + if ($provider === TrackingPixelProvider::GOOGLE_TAG_MANAGER && app(AccountUserAccessService::class)->isWithheldFromCurrentUser(Permission::MANAGE_ACCOUNT) && ! in_array($pixelId, $this->existingTagManagerIds(), true) diff --git a/backend/app/Jobs/ApiKey/SendApiKeyExpiryRemindersJob.php b/backend/app/Jobs/ApiKey/SendApiKeyExpiryRemindersJob.php new file mode 100644 index 0000000000..cf67e3c78b --- /dev/null +++ b/backend/app/Jobs/ApiKey/SendApiKeyExpiryRemindersJob.php @@ -0,0 +1,22 @@ +sendDueReminders(); + } +} diff --git a/backend/app/Listeners/ApiKey/RecordApiKeyUsageListener.php b/backend/app/Listeners/ApiKey/RecordApiKeyUsageListener.php new file mode 100644 index 0000000000..372e391cf7 --- /dev/null +++ b/backend/app/Listeners/ApiKey/RecordApiKeyUsageListener.php @@ -0,0 +1,32 @@ +token->last_used_at; + + if ($lastUsedAt !== null && $lastUsedAt->gt(now()->subMinutes(self::RECORD_INTERVAL_MINUTES))) { + return; + } + + $this->apiKeyRepository->updateWhere( + attributes: [PersonalAccessTokenDomainObject::LAST_USED_AT => now()->toDateTimeString()], + where: [PersonalAccessTokenDomainObject::ID => $event->token->getKey()], + ); + } +} diff --git a/backend/app/Mail/User/ApiKeyCreatedMail.php b/backend/app/Mail/User/ApiKeyCreatedMail.php new file mode 100644 index 0000000000..4c61ca714c --- /dev/null +++ b/backend/app/Mail/User/ApiKeyCreatedMail.php @@ -0,0 +1,45 @@ + $this->keyName, + 'accountName' => $this->accountName, + 'accessLabel' => $this->access === ApiKeyAccess::READ_WRITE ? __('Read and write') : __('Read-only'), + 'expiresAt' => $this->expiresAt, + 'apiKeysUrl' => Url::getFrontEndUrlFromConfig(Url::API_KEYS), + ] + ); + } +} diff --git a/backend/app/Mail/User/ApiKeyExpiringMail.php b/backend/app/Mail/User/ApiKeyExpiringMail.php new file mode 100644 index 0000000000..699acad4a3 --- /dev/null +++ b/backend/app/Mail/User/ApiKeyExpiringMail.php @@ -0,0 +1,42 @@ + $this->keyName]), + ); + } + + public function content(): Content + { + return new Content( + markdown: 'emails.api-keys.api-key-expiring', + with: [ + 'keyName' => $this->keyName, + 'accountName' => $this->accountName, + 'expiresAt' => $this->expiresAt, + 'apiKeysUrl' => Url::getFrontEndUrlFromConfig(Url::API_KEYS), + ] + ); + } +} diff --git a/backend/app/Mail/User/ApiKeyRevokedMail.php b/backend/app/Mail/User/ApiKeyRevokedMail.php new file mode 100644 index 0000000000..1c8ef665ad --- /dev/null +++ b/backend/app/Mail/User/ApiKeyRevokedMail.php @@ -0,0 +1,40 @@ + $this->keyName, + 'accountName' => $this->accountName, + 'apiKeysUrl' => Url::getFrontEndUrlFromConfig(Url::API_KEYS), + ] + ); + } +} diff --git a/backend/app/Models/PersonalAccessToken.php b/backend/app/Models/PersonalAccessToken.php new file mode 100644 index 0000000000..7cec5d5b23 --- /dev/null +++ b/backend/app/Models/PersonalAccessToken.php @@ -0,0 +1,15 @@ +belongsTo(User::class, 'tokenable_id'); + } +} diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index bdf41a00e6..d043d3aa92 100644 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -20,6 +20,7 @@ use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Notifications\Notifiable; use Lab404\Impersonate\Models\Impersonate; +use Laravel\Sanctum\HasApiTokens; use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject; use RuntimeException; @@ -32,6 +33,7 @@ class User extends BaseModel implements AuthenticatableContract, AuthorizableCon use Authenticatable; use Authorizable; use CanResetPassword; + use HasApiTokens; use HasFactory; use Impersonate; use MustVerifyEmail; diff --git a/backend/app/OpenApi/IntegrationApiReference.php b/backend/app/OpenApi/IntegrationApiReference.php new file mode 100644 index 0000000000..8c6c10e29d --- /dev/null +++ b/backend/app/OpenApi/IntegrationApiReference.php @@ -0,0 +1,128 @@ +app->basePath(self::RELATIVE_PATH); + } + + public function generate(): string + { + $document = json_decode( + json_encode($this->app->make(Generator::class)(Scramble::getGeneratorConfig('default')), JSON_THROW_ON_ERROR), + true, + flags: JSON_THROW_ON_ERROR, + ); + + unset($document['servers'], $document['info']['version']); + + $document['paths'] = array_filter( + array_map($this->withoutSessionOnlyOperations(...), $document['paths']), + static fn (array $pathItem, string $path) => $pathItem !== [] && ! self::isExcludedPath($path), + ARRAY_FILTER_USE_BOTH, + ); + + $document = $this->withReachableSchemasOnly($document); + + $json = json_encode($document, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); + + return preg_replace_callback('/^(?: {4})+/m', static fn (array $indent) => substr($indent[0], 0, intdiv(strlen($indent[0]), 2)), $json)."\n"; + } + + /** + * @return array + */ + public function published(): array + { + $document = json_decode((string) file_get_contents($this->path()), true, flags: JSON_THROW_ON_ERROR); + $document['info']['version'] = trim((string) file_get_contents($this->app->basePath('VERSION'))); + + return $document; + } + + private static function isExcludedPath(string $path): bool + { + foreach (self::EXCLUDED_PATH_PREFIXES as $prefix) { + if (str_starts_with($path, $prefix)) { + return true; + } + } + + return false; + } + + /** + * @param array $document + * @return array + */ + private function withReachableSchemasOnly(array $document): array + { + $schemas = $document['components']['schemas'] ?? []; + $pending = self::schemaRefs([$document['paths'], array_diff_key($document['components'] ?? [], ['schemas' => true])]); + $reachable = []; + + while ($pending !== []) { + $name = array_pop($pending); + + if (isset($reachable[$name]) || ! isset($schemas[$name])) { + continue; + } + + $reachable[$name] = true; + array_push($pending, ...self::schemaRefs($schemas[$name])); + } + + $document['components']['schemas'] = array_intersect_key($schemas, $reachable); + + return $document; + } + + /** + * @return string[] + */ + private static function schemaRefs(mixed $node): array + { + preg_match_all(self::SCHEMA_REF_PATTERN, json_encode($node, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), $matches); + + return $matches[1]; + } + + /** + * @param array $pathItem + * @return array + */ + private function withoutSessionOnlyOperations(array $pathItem): array + { + $operations = array_filter( + $pathItem, + static fn ($operation, string $key) => in_array($key, self::HTTP_METHODS, true) + && ! ($operation['x-'.self::SESSION_ONLY_EXTENSION] ?? false), + ARRAY_FILTER_USE_BOTH, + ); + + return $operations === [] ? [] : $operations + array_diff_key($pathItem, array_flip(self::HTTP_METHODS)); + } +} diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php index 2e0f73ad15..6b7f710ca9 100644 --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -17,6 +17,7 @@ use HiEvents\Models\Organizer; use HiEvents\Models\Profile; use HiEvents\Services\Domain\Auth\Access\AccountUserAccessService; +use HiEvents\Services\Domain\Auth\ApiKey\ApiKeyService; use HiEvents\Services\Infrastructure\CurrencyConversion\CurrencyConversionClientInterface; use HiEvents\Services\Infrastructure\CurrencyConversion\NoOpCurrencyConversionClient; use HiEvents\Services\Infrastructure\CurrencyConversion\OpenExchangeRatesCurrencyConversionClient; @@ -33,6 +34,8 @@ use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; +use Laravel\Sanctum\PersonalAccessToken; +use Laravel\Sanctum\Sanctum; use PHPOpenSourceSaver\JWTAuth\Http\Parser\AuthHeaders; use PHPOpenSourceSaver\JWTAuth\Http\Parser\Cookies; use Psr\Clock\ClockInterface; @@ -67,6 +70,16 @@ public function boot(): void $this->disableLazyLoading(); $this->registerMorphMaps(); + + $this->restrictApiKeysToActiveMembers(); + } + + private function restrictApiKeysToActiveMembers(): void + { + Sanctum::authenticateAccessTokensUsing( + static fn (PersonalAccessToken $apiKey, bool $isValid): bool => $isValid + && app(ApiKeyService::class)->isUsableBy((int) $apiKey->getAttribute('tokenable_id'), (int) $apiKey->getAttribute('account_id')), + ); } private function restrictJwtSourcesToHeaderAndCookie(): void diff --git a/backend/app/Providers/RepositoryServiceProvider.php b/backend/app/Providers/RepositoryServiceProvider.php index 96a09ccf19..8773af9fc0 100644 --- a/backend/app/Providers/RepositoryServiceProvider.php +++ b/backend/app/Providers/RepositoryServiceProvider.php @@ -70,6 +70,7 @@ use HiEvents\Repository\Eloquent\OutgoingMessageRepository; use HiEvents\Repository\Eloquent\PasswordResetRepository; use HiEvents\Repository\Eloquent\PasswordResetTokenRepository; +use HiEvents\Repository\Eloquent\PersonalAccessTokenRepository; use HiEvents\Repository\Eloquent\ProductCategoryRepository; use HiEvents\Repository\Eloquent\ProductOccurrenceVisibilityRepository; use HiEvents\Repository\Eloquent\ProductPriceOccurrenceOverrideRepository; @@ -147,6 +148,7 @@ use HiEvents\Repository\Interfaces\OutgoingMessageRepositoryInterface; use HiEvents\Repository\Interfaces\PasswordResetRepositoryInterface; use HiEvents\Repository\Interfaces\PasswordResetTokenRepositoryInterface; +use HiEvents\Repository\Interfaces\PersonalAccessTokenRepositoryInterface; use HiEvents\Repository\Interfaces\ProductCategoryRepositoryInterface; use HiEvents\Repository\Interfaces\ProductOccurrenceVisibilityRepositoryInterface; use HiEvents\Repository\Interfaces\ProductPriceOccurrenceOverrideRepositoryInterface; @@ -204,6 +206,7 @@ class RepositoryServiceProvider extends ServiceProvider PromoCodeRepositoryInterface::class => PromoCodeRepository::class, MessageRepositoryInterface::class => MessageRepository::class, PasswordResetTokenRepositoryInterface::class => PasswordResetTokenRepository::class, + PersonalAccessTokenRepositoryInterface::class => PersonalAccessTokenRepository::class, PasswordResetRepositoryInterface::class => PasswordResetRepository::class, TaxAndFeeRepositoryInterface::class => TaxAndFeeRepository::class, ImageRepositoryInterface::class => ImageRepository::class, diff --git a/backend/app/Providers/ScrambleServiceProvider.php b/backend/app/Providers/ScrambleServiceProvider.php index 99a23f04e6..76f5286882 100644 --- a/backend/app/Providers/ScrambleServiceProvider.php +++ b/backend/app/Providers/ScrambleServiceProvider.php @@ -9,6 +9,7 @@ use Dedoc\Scramble\Support\Generator\Operation; use Dedoc\Scramble\Support\Generator\SecurityScheme; use Dedoc\Scramble\Support\RouteInfo; +use HiEvents\OpenApi\IntegrationApiReference; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Routing\Route; use Illuminate\Support\Facades\Gate; @@ -17,6 +18,8 @@ class ScrambleServiceProvider extends ServiceProvider { + private const LICENSING_NAMESPACE = 'HiEvents\\Enterprise\\Licensing\\'; + private const ACTION_NAMESPACE_PATTERN = '/^HiEvents\\\\(Enterprise\\\\(\w+)\\\\)?Http\\\\Actions\\\\/'; public function register(): void @@ -24,7 +27,7 @@ public function register(): void $this->app->bind( MiddlewareAuthSecurityStrategy::class, static fn () => new MiddlewareAuthSecurityStrategy( - scheme: SecurityScheme::http('bearer', 'JWT'), + scheme: SecurityScheme::http('bearer')->setDescription('An API key (`hie_…`) or a session token from `POST /auth/login`.'), ), ); } @@ -42,6 +45,10 @@ public function boot(): void return false; } + if (str_starts_with($route->getActionName(), self::LICENSING_NAMESPACE)) { + return false; + } + return preg_match(self::ACTION_NAMESPACE_PATTERN, $route->getActionName()) === 1; }) ->withOperationTransformers(static function (Operation $operation, RouteInfo $routeInfo): void { @@ -49,10 +56,13 @@ public function boot(): void ->replaceMatches(self::ACTION_NAMESPACE_PATTERN, '${2}\\\\') ->ltrim('\\') ->beforeLast('\\') - ->replace('\\', ' / '); + ->explode('\\') + ->filter() + ->map(static fn (string $segment) => Str::headline($segment)) + ->implode(' / '); - if ($tag->isNotEmpty()) { - $operation->tags = [$tag->toString()]; + if ($tag !== '') { + $operation->tags = [$tag]; } if ($operation->summary === '') { @@ -62,6 +72,11 @@ public function boot(): void if (str_starts_with($routeInfo->route->uri(), 'public/') && ! str_contains($operation->summary, '(public)')) { $operation->summary($operation->summary.' (public)'); } + + if (in_array('auth:api', $routeInfo->route->gatherMiddleware(), true)) { + $operation->description(trim($operation->description."\n\n".'Not available to API keys: sign in to call this endpoint.')); + $operation->setExtensionProperty(IntegrationApiReference::SESSION_ONLY_EXTENSION, true); + } }); } diff --git a/backend/app/Repository/Eloquent/PersonalAccessTokenRepository.php b/backend/app/Repository/Eloquent/PersonalAccessTokenRepository.php new file mode 100644 index 0000000000..7045ceeacc --- /dev/null +++ b/backend/app/Repository/Eloquent/PersonalAccessTokenRepository.php @@ -0,0 +1,42 @@ + + */ +class PersonalAccessTokenRepository extends BaseRepository implements PersonalAccessTokenRepositoryInterface +{ + protected function getModel(): string + { + return PersonalAccessToken::class; + } + + public function getDomainObject(): string + { + return PersonalAccessTokenDomainObject::class; + } + + public function findDueForExpiryReminder(CarbonImmutable $now, int $windowDays, int $minimumLifetimeDays): Collection + { + return $this->runQuery(function () use ($now, $windowDays, $minimumLifetimeDays) { + $this->loadRelation(new Relationship(UserDomainObject::class, name: 'user')); + + return $this->handleResults($this->model + ->whereNull(PersonalAccessTokenDomainObject::EXPIRY_REMINDER_SENT_AT) + ->where(PersonalAccessTokenDomainObject::EXPIRES_AT, '>', $now) + ->where(PersonalAccessTokenDomainObject::EXPIRES_AT, '<=', $now->addDays($windowDays)) + ->whereRaw('created_at <= expires_at - make_interval(days => ?)', [$minimumLifetimeDays]) + ->orderBy(PersonalAccessTokenDomainObject::ID) + ->get()); + }); + } +} diff --git a/backend/app/Repository/Interfaces/PersonalAccessTokenRepositoryInterface.php b/backend/app/Repository/Interfaces/PersonalAccessTokenRepositoryInterface.php new file mode 100644 index 0000000000..43993fd5a4 --- /dev/null +++ b/backend/app/Repository/Interfaces/PersonalAccessTokenRepositoryInterface.php @@ -0,0 +1,18 @@ + + */ +interface PersonalAccessTokenRepositoryInterface extends RepositoryInterface +{ + /** + * @return Collection + */ + public function findDueForExpiryReminder(CarbonImmutable $now, int $windowDays, int $minimumLifetimeDays): Collection; +} diff --git a/backend/app/Resources/ApiKey/ApiKeyResource.php b/backend/app/Resources/ApiKey/ApiKeyResource.php new file mode 100644 index 0000000000..38a52bc1b5 --- /dev/null +++ b/backend/app/Resources/ApiKey/ApiKeyResource.php @@ -0,0 +1,38 @@ +getUser(); + + return [ + 'id' => $this->getId(), + 'name' => $this->getName(), + 'token_hint' => $this->getTokenHint(), + /** @var 'READ_ONLY'|'READ_WRITE' */ + 'access' => $this->getAccess()->value, + 'last_used_at' => $this->getLastUsedAt(), + 'expires_at' => $this->getExpiresAt(), + 'created_at' => $this->getCreatedAt(), + 'is_expired' => $this->getExpiresAt() !== null && CarbonImmutable::parse($this->getExpiresAt())->isPast(), + 'is_own' => $this->getTokenableId() === $request->user()?->getAuthIdentifier(), + /** @var array{id: int, full_name: string, email: string}|null */ + 'owner' => $owner === null ? null : [ + 'id' => $owner->getId(), + 'full_name' => $owner->getFullName(), + 'email' => $owner->getEmail(), + ], + ]; + } +} diff --git a/backend/app/Resources/ApiKey/CreatedApiKeyResource.php b/backend/app/Resources/ApiKey/CreatedApiKeyResource.php new file mode 100644 index 0000000000..498ecab4a2 --- /dev/null +++ b/backend/app/Resources/ApiKey/CreatedApiKeyResource.php @@ -0,0 +1,21 @@ +apiKey))->toArray($request), + 'key' => $this->plainTextKey, + ]; + } +} diff --git a/backend/app/Resources/User/MeResource.php b/backend/app/Resources/User/MeResource.php index a435b9d81a..c44f03cbd2 100644 --- a/backend/app/Resources/User/MeResource.php +++ b/backend/app/Resources/User/MeResource.php @@ -7,10 +7,11 @@ use HiEvents\Enterprise\Licensing\LicenceService; use HiEvents\Enterprise\Licensing\LicensedFeature; use HiEvents\Enterprise\Licensing\LicensedFeatureUsageService; +use HiEvents\Services\Domain\Auth\AuthUserService; +use HiEvents\Services\Domain\Auth\Sso\Enterprise\SsoEnforcementService; use HiEvents\Services\Domain\Auth\TwoFactor\TwoFactorRequirementService; use HiEvents\Services\Domain\FeatureFlag\FeatureFlagService; use Illuminate\Http\Request; -use PHPOpenSourceSaver\JWTAuth\Exceptions\JWTException; /** * @mixin UserDomainObject @@ -32,8 +33,10 @@ public function toArray(Request $request): array ...parent::toArray($request), 'two_factor_setup_required' => $accountId !== null && $this->getTwoFactorConfirmedAt() === null - && ! $this->isImpersonating() + && ! app(AuthUserService::class)->isImpersonating() && app(TwoFactorRequirementService::class)->isRequiredByAccount($accountId), + /** @var bool */ + 'password_reset_allowed' => app(SsoEnforcementService::class)->isPasswordResetAllowed($this->resource), /** @var array */ 'feature_flags' => (object) ($accountId !== null ? app(FeatureFlagService::class)->getEnabledFlagsForAccount($accountId) @@ -52,13 +55,4 @@ public function toArray(Request $request): array ], ]; } - - private function isImpersonating(): bool - { - try { - return (bool) auth()->payload()->get('is_impersonating', false); - } catch (JWTException) { - return false; - } - } } diff --git a/backend/app/Resources/User/TwoFactor/TwoFactorStatusResource.php b/backend/app/Resources/User/TwoFactor/TwoFactorStatusResource.php index 1bf216e882..5cfe88f42d 100644 --- a/backend/app/Resources/User/TwoFactor/TwoFactorStatusResource.php +++ b/backend/app/Resources/User/TwoFactor/TwoFactorStatusResource.php @@ -23,6 +23,8 @@ public function toArray(Request $request): array 'trusted_devices' => TrustedDeviceResource::collection($this->trustedDevices), /** @var array */ 'required_by_accounts' => $this->requiredByAccounts->all(), + /** @var array */ + 'required_by_sso_accounts' => $this->requiredBySsoAccounts->all(), ]; } } diff --git a/backend/app/Resources/User/UserResource.php b/backend/app/Resources/User/UserResource.php index 57e087abe5..1c3fc18455 100644 --- a/backend/app/Resources/User/UserResource.php +++ b/backend/app/Resources/User/UserResource.php @@ -2,11 +2,11 @@ namespace HiEvents\Resources\User; -use Exception; use HiEvents\DomainObjects\Enums\Permission; use HiEvents\DomainObjects\UserDomainObject; use HiEvents\Resources\BaseResource; use HiEvents\Services\Domain\Auth\Access\AccountUserAccessService; +use HiEvents\Services\Domain\Auth\AuthUserService; use HiEvents\Services\Domain\Auth\PasswordLoginPolicy; use Illuminate\Http\Request; @@ -17,14 +17,9 @@ class UserResource extends BaseResource { public function toArray(Request $request): array { - $isImpersonating = false; - $impersonatorId = null; - try { - $isImpersonating = (bool) auth()->payload()->get('is_impersonating', false); - $impersonatorId = $isImpersonating ? auth()->payload()->get('impersonator_id') : null; - } catch (Exception) { - // Not authenticated or no JWT token - } + $authUserService = app(AuthUserService::class); + $isImpersonating = $authUserService->isImpersonating(); + $impersonatorId = $authUserService->getImpersonatorId(); return [ 'id' => $this->getId(), diff --git a/backend/app/Services/Application/Handlers/Account/CreateAccountHandler.php b/backend/app/Services/Application/Handlers/Account/CreateAccountHandler.php index d79b1b9e62..c6a4e379a6 100644 --- a/backend/app/Services/Application/Handlers/Account/CreateAccountHandler.php +++ b/backend/app/Services/Application/Handlers/Account/CreateAccountHandler.php @@ -20,6 +20,7 @@ use HiEvents\Services\Application\Handlers\Account\Exceptions\AccountRegistrationDisabledException; use HiEvents\Services\Domain\Account\AccountUserAssociationService; use HiEvents\Services\Domain\Account\AttributionSourceClassifier; +use HiEvents\Services\Domain\Auth\Sso\Enterprise\SsoEnforcementService; use HiEvents\Services\Domain\User\EmailConfirmationService; use Illuminate\Config\Repository; use Illuminate\Database\DatabaseManager; @@ -42,6 +43,7 @@ public function __construct( private readonly AccountConfigurationRepositoryInterface $accountConfigurationRepository, private readonly AccountAttributionRepositoryInterface $accountAttributionRepository, private readonly AttributionSourceClassifier $attributionSourceClassifier, + private readonly SsoEnforcementService $ssoEnforcementService, private readonly LoggerInterface $logger, ) {} @@ -54,6 +56,8 @@ public function handle(CreateAccountDTO $accountData): AccountDomainObject throw new AccountRegistrationDisabledException; } + $this->ssoEnforcementService->assertSignupAllowed($accountData->email); + $requiresVerification = $this->config->get('app.saas_mode_enabled') && ! $accountData->email_verified; $passwordHash = $accountData->password !== null ? $this->hashManager->make($accountData->password) : null; diff --git a/backend/app/Services/Application/Handlers/ApiKey/CreateApiKeyHandler.php b/backend/app/Services/Application/Handlers/ApiKey/CreateApiKeyHandler.php new file mode 100644 index 0000000000..a67a03cbd6 --- /dev/null +++ b/backend/app/Services/Application/Handlers/ApiKey/CreateApiKeyHandler.php @@ -0,0 +1,57 @@ +userRepository->findById($dto->userId); + + if ($user->getEmailVerifiedAt() === null) { + throw new ResourceConflictException(__('Confirm your email address before creating an API key.')); + } + + if ($dto->access === ApiKeyAccess::READ_WRITE && ! $dto->canEdit) { + throw new PermissionRequiredException(__('You have view-only access, so you can only create read-only API keys.')); + } + + if ($this->apiKeyService->countActiveKeys($dto->userId, $dto->accountId) >= ApiKeyService::MAX_ACTIVE_KEYS_PER_MEMBER) { + throw new ResourceConflictException(__( + 'You can have up to :max active API keys. Revoke one you no longer use and try again.', + ['max' => ApiKeyService::MAX_ACTIVE_KEYS_PER_MEMBER], + )); + } + + $this->identityConfirmationService->confirm($user, $dto->password, $dto->reauthProof, $dto->tokenId); + + return $this->apiKeyService->create( + user: $user, + accountId: $dto->accountId, + name: $dto->name, + access: $dto->access, + expiresInDays: $dto->expiresInDays, + ); + } +} diff --git a/backend/app/Services/Application/Handlers/ApiKey/DTO/CreateApiKeyDTO.php b/backend/app/Services/Application/Handlers/ApiKey/DTO/CreateApiKeyDTO.php new file mode 100644 index 0000000000..e956b29e11 --- /dev/null +++ b/backend/app/Services/Application/Handlers/ApiKey/DTO/CreateApiKeyDTO.php @@ -0,0 +1,21 @@ +apiKeyRepository + ->loadRelation(new Relationship(UserDomainObject::class, name: 'user')) + ->findFirstWhere([ + PersonalAccessTokenDomainObject::ID => $dto->apiKeyId, + PersonalAccessTokenDomainObject::ACCOUNT_ID => $dto->accountId, + ]); + + $isOwnKey = $apiKey?->getTokenableId() === $dto->userId; + + if ($apiKey === null || (! $isOwnKey && ! $dto->canManageTeam)) { + throw new ResourceNotFoundException(__('API key not found')); + } + + $this->apiKeyRepository->deleteById($apiKey->getId()); + + $owner = $apiKey->getUser(); + + if ($isOwnKey || $owner === null) { + return; + } + + $this->mailer + ->to($owner->getEmail()) + ->locale($owner->getLocale()) + ->send(new ApiKeyRevokedMail( + keyName: $apiKey->getName(), + accountName: $this->accountRepository->findById($dto->accountId)->getName(), + )); + } +} diff --git a/backend/app/Services/Application/Handlers/ApiKey/GetApiKeysHandler.php b/backend/app/Services/Application/Handlers/ApiKey/GetApiKeysHandler.php new file mode 100644 index 0000000000..ddc64610b4 --- /dev/null +++ b/backend/app/Services/Application/Handlers/ApiKey/GetApiKeysHandler.php @@ -0,0 +1,41 @@ + + */ + public function handle(int $accountId, int $userId, bool $includeTeamKeys): Collection + { + $where = [ + PersonalAccessTokenDomainObject::ACCOUNT_ID => $accountId, + ]; + + if (! $includeTeamKeys) { + $where[PersonalAccessTokenDomainObject::TOKENABLE_ID] = $userId; + } + + return $this->apiKeyRepository + ->loadRelation(new Relationship(UserDomainObject::class, name: 'user')) + ->findWhere( + where: $where, + orderAndDirections: [ + new OrderAndDirection(PersonalAccessTokenDomainObject::CREATED_AT, OrderAndDirection::DIRECTION_DESC), + new OrderAndDirection(PersonalAccessTokenDomainObject::ID, OrderAndDirection::DIRECTION_DESC), + ], + ); + } +} diff --git a/backend/app/Services/Application/Handlers/Auth/Sso/ExchangeSsoCodeHandler.php b/backend/app/Services/Application/Handlers/Auth/Sso/ExchangeSsoCodeHandler.php index dc29de33c8..436652534d 100644 --- a/backend/app/Services/Application/Handlers/Auth/Sso/ExchangeSsoCodeHandler.php +++ b/backend/app/Services/Application/Handlers/Auth/Sso/ExchangeSsoCodeHandler.php @@ -9,6 +9,7 @@ use HiEvents\DomainObjects\Enums\SsoTicketType; use HiEvents\Exceptions\ResourceConflictException; use HiEvents\Exceptions\SsoException; +use HiEvents\Exceptions\SsoRequiredException; use HiEvents\Exceptions\UnauthorizedException; use HiEvents\Repository\Interfaces\UserRepositoryInterface; use HiEvents\Services\Application\Handlers\Auth\Sso\DTO\ExchangeSsoCodeDTO; @@ -24,6 +25,7 @@ use HiEvents\Services\Domain\Auth\Sso\Enterprise\AccountSsoConnectionService; use HiEvents\Services\Domain\Auth\Sso\Enterprise\AccountSsoDomainService; use HiEvents\Services\Domain\Auth\Sso\Enterprise\EnterpriseSsoResolver; +use HiEvents\Services\Domain\Auth\Sso\Enterprise\SsoEnforcementService; use HiEvents\Services\Domain\Auth\Sso\OidcClient; use HiEvents\Services\Domain\Auth\Sso\SsoIdentityResolver; use HiEvents\Services\Domain\Auth\Sso\SsoProviderRegistry; @@ -54,6 +56,7 @@ public function __construct( private readonly EnterpriseSsoResolver $enterpriseResolver, private readonly AccountSsoConnectionService $connectionService, private readonly AccountSsoDomainService $domainService, + private readonly SsoEnforcementService $enforcementService, ) {} /** @@ -162,6 +165,7 @@ private function test(SsoProviderConfig $provider, SsoStateDTO $state, SsoIdenti /** * @throws SsoException + * @throws SsoRequiredException */ private function signupRequired(SsoProviderConfig $provider, SsoStateDTO $state, SsoIdentityClaims $claims, string $bindingHash): SsoExchangeResult { @@ -176,6 +180,8 @@ private function signupRequired(SsoProviderConfig $provider, SsoStateDTO $state, ); } + $this->enforcementService->assertSignupAllowed($claims->email); + return new SsoExchangeResult( status: SsoExchangeStatus::SIGNUP_REQUIRED, providerName: $provider->name, diff --git a/backend/app/Services/Application/Handlers/User/TwoFactor/DTO/TwoFactorStatusDTO.php b/backend/app/Services/Application/Handlers/User/TwoFactor/DTO/TwoFactorStatusDTO.php index 9b3a7e8bc0..fd58cbb9c4 100644 --- a/backend/app/Services/Application/Handlers/User/TwoFactor/DTO/TwoFactorStatusDTO.php +++ b/backend/app/Services/Application/Handlers/User/TwoFactor/DTO/TwoFactorStatusDTO.php @@ -13,5 +13,6 @@ public function __construct( public readonly int $recoveryCodesRemaining, public readonly Collection $trustedDevices, public readonly Collection $requiredByAccounts, + public readonly Collection $requiredBySsoAccounts, ) {} } diff --git a/backend/app/Services/Application/Handlers/User/TwoFactor/GetTwoFactorStatusHandler.php b/backend/app/Services/Application/Handlers/User/TwoFactor/GetTwoFactorStatusHandler.php index 5527a2c3a1..d8314d3edc 100644 --- a/backend/app/Services/Application/Handlers/User/TwoFactor/GetTwoFactorStatusHandler.php +++ b/backend/app/Services/Application/Handlers/User/TwoFactor/GetTwoFactorStatusHandler.php @@ -43,10 +43,8 @@ public function handle(int $userId): TwoFactorStatusDTO ], ) : collect(), - requiredByAccounts: $this->twoFactorRequirementService->namesOfAccountsRequiringTwoFactor($userId) - ->merge($this->ssoEnforcementService->namesOfOwnedEnforcingAccounts($userId)) - ->unique() - ->values(), + requiredByAccounts: $this->twoFactorRequirementService->namesOfAccountsRequiringTwoFactor($userId), + requiredBySsoAccounts: $this->ssoEnforcementService->namesOfOwnedEnforcingAccounts($userId), ); } } diff --git a/backend/app/Services/Application/Handlers/User/UpdateUserHandler.php b/backend/app/Services/Application/Handlers/User/UpdateUserHandler.php index a377c2ce64..92f5174f46 100644 --- a/backend/app/Services/Application/Handlers/User/UpdateUserHandler.php +++ b/backend/app/Services/Application/Handlers/User/UpdateUserHandler.php @@ -14,6 +14,7 @@ use HiEvents\Services\Domain\Auth\Access\AdminAccessNotificationService; use HiEvents\Services\Domain\Auth\Access\DTO\AccountUserAccessData; use HiEvents\Services\Domain\Auth\Access\Exception\InvalidAccessGrantException; +use HiEvents\Services\Domain\Auth\ApiKey\ApiKeyService; use Illuminate\Database\DatabaseManager; use Psr\Log\LoggerInterface; use Throwable; @@ -27,6 +28,7 @@ public function __construct( private readonly AccountUserAccessUpdateService $accessUpdateService, private readonly AdminAccessNotificationService $adminAccessNotificationService, private readonly DatabaseManager $databaseManager, + private readonly ApiKeyService $apiKeyService, ) {} /** @@ -99,6 +101,10 @@ private function updateUser(UpdateUserDTO $updateUserData): UserDomainObject ] ); + if ($updateUserData->status === UserStatus::INACTIVE) { + $this->apiKeyService->revokeAllForMember($updateUserData->id, $updateUserData->account_id); + } + if ($updateUserData->access !== null || ! $updateUserData->role->canBeRestricted()) { $this->accessUpdateService->update( accountUser: $accountUser, diff --git a/backend/app/Services/Domain/Account/AccountAnonymizationService.php b/backend/app/Services/Domain/Account/AccountAnonymizationService.php index e3f5f5d4a0..1bc214ff29 100644 --- a/backend/app/Services/Domain/Account/AccountAnonymizationService.php +++ b/backend/app/Services/Domain/Account/AccountAnonymizationService.php @@ -5,6 +5,7 @@ use HiEvents\Services\Domain\Account\Anonymization\AccountDataResolver; use HiEvents\Services\Domain\Account\Anonymization\Anonymizers\AccountAnonymizer; use HiEvents\Services\Domain\Account\Anonymization\Anonymizers\ActivityLogAnonymizer; +use HiEvents\Services\Domain\Account\Anonymization\Anonymizers\ApiKeyAnonymizer; use HiEvents\Services\Domain\Account\Anonymization\Anonymizers\EventContentAnonymizer; use HiEvents\Services\Domain\Account\Anonymization\Anonymizers\ImageAnonymizer; use HiEvents\Services\Domain\Account\Anonymization\Anonymizers\OrderAnonymizer; @@ -27,6 +28,7 @@ public function __construct( private readonly ActivityLogAnonymizer $activityLogAnonymizer, private readonly PartnerAnonymizer $partnerAnonymizer, private readonly SsoConnectionAnonymizer $ssoConnectionAnonymizer, + private readonly ApiKeyAnonymizer $apiKeyAnonymizer, private readonly UserAnonymizer $userAnonymizer, private readonly OrganizerAnonymizer $organizerAnonymizer, private readonly ImageAnonymizer $imageAnonymizer, @@ -54,6 +56,7 @@ public function anonymizeAccount(int $accountId): array $this->activityLogAnonymizer, $this->partnerAnonymizer, $this->ssoConnectionAnonymizer, + $this->apiKeyAnonymizer, $this->userAnonymizer, $this->organizerAnonymizer, $this->imageAnonymizer, diff --git a/backend/app/Services/Domain/Account/AccountHardDeletionService.php b/backend/app/Services/Domain/Account/AccountHardDeletionService.php index 99db984b18..53ee0b2089 100644 --- a/backend/app/Services/Domain/Account/AccountHardDeletionService.php +++ b/backend/app/Services/Domain/Account/AccountHardDeletionService.php @@ -155,6 +155,10 @@ private function deleteAccountData(AnonymizationContext $context): array ->where('account_id', $context->accountId) ->delete(); + $manifest['api_keys'] = $connection->table('personal_access_tokens') + ->where('account_id', $context->accountId) + ->delete(); + $manifest['roles'] = $connection->table('roles') ->where('account_id', $context->accountId) ->delete(); diff --git a/backend/app/Services/Domain/Account/Anonymization/Anonymizers/ApiKeyAnonymizer.php b/backend/app/Services/Domain/Account/Anonymization/Anonymizers/ApiKeyAnonymizer.php new file mode 100644 index 0000000000..252b042505 --- /dev/null +++ b/backend/app/Services/Domain/Account/Anonymization/Anonymizers/ApiKeyAnonymizer.php @@ -0,0 +1,26 @@ +executor->delete( + query: $this->databaseManager->table('personal_access_tokens')->where('account_id', $context->accountId), + entity: 'personal_access_tokens', + ), + ]; + } +} diff --git a/backend/app/Services/Domain/Auth/ApiKey/ApiKeyExpiryReminderService.php b/backend/app/Services/Domain/Auth/ApiKey/ApiKeyExpiryReminderService.php new file mode 100644 index 0000000000..167a5e242d --- /dev/null +++ b/backend/app/Services/Domain/Auth/ApiKey/ApiKeyExpiryReminderService.php @@ -0,0 +1,61 @@ +apiKeyRepository->findDueForExpiryReminder($now, self::REMINDER_WINDOW_DAYS, self::MINIMUM_LIFETIME_DAYS); + + if ($apiKeys->isEmpty()) { + return; + } + + $accountNames = $this->accountRepository + ->findWhereIn('id', $apiKeys->map(fn (PersonalAccessTokenDomainObject $apiKey) => $apiKey->getAccountId())->unique()->values()->all()) + ->mapWithKeys(fn (AccountDomainObject $account) => [$account->getId() => $account->getName()]); + + foreach ($apiKeys as $apiKey) { + $user = $apiKey->getUser(); + + if ($user !== null + && $accountNames->has($apiKey->getAccountId()) + && $this->apiKeyService->isUsableBy($user->getId(), $apiKey->getAccountId())) { + $this->mailer + ->to($user->getEmail()) + ->locale($user->getLocale()) + ->send(new ApiKeyExpiringMail( + keyName: $apiKey->getName(), + accountName: $accountNames->get($apiKey->getAccountId()), + expiresAt: CarbonImmutable::parse($apiKey->getExpiresAt())->toDateString(), + )); + } + + $this->apiKeyRepository->updateWhere( + attributes: [PersonalAccessTokenDomainObject::EXPIRY_REMINDER_SENT_AT => $now->toDateTimeString()], + where: [PersonalAccessTokenDomainObject::ID => $apiKey->getId()], + ); + } + } +} diff --git a/backend/app/Services/Domain/Auth/ApiKey/ApiKeyService.php b/backend/app/Services/Domain/Auth/ApiKey/ApiKeyService.php new file mode 100644 index 0000000000..2b10f91951 --- /dev/null +++ b/backend/app/Services/Domain/Auth/ApiKey/ApiKeyService.php @@ -0,0 +1,103 @@ +generateKey(); + $expiresAt = $expiresInDays === null ? null : CarbonImmutable::now()->addDays($expiresInDays); + + /** @var PersonalAccessTokenDomainObject $apiKey */ + $apiKey = $this->apiKeyRepository->create([ + PersonalAccessTokenDomainObject::TOKENABLE_TYPE => User::class, + PersonalAccessTokenDomainObject::TOKENABLE_ID => $user->getId(), + PersonalAccessTokenDomainObject::ACCOUNT_ID => $accountId, + PersonalAccessTokenDomainObject::NAME => $name, + PersonalAccessTokenDomainObject::TOKEN => hash('sha256', $plainTextKey), + PersonalAccessTokenDomainObject::TOKEN_HINT => substr($plainTextKey, -4), + PersonalAccessTokenDomainObject::ABILITIES => json_encode($access->abilities(), JSON_THROW_ON_ERROR), + PersonalAccessTokenDomainObject::EXPIRES_AT => $expiresAt?->toDateTimeString(), + ]); + + $this->mailer + ->to($user->getEmail()) + ->locale($user->getLocale()) + ->send(new ApiKeyCreatedMail( + keyName: $name, + accountName: $this->accountRepository->findById($accountId)->getName(), + access: $access, + expiresAt: $expiresAt?->toDateString(), + )); + + return new CreatedApiKeyDTO(apiKey: $apiKey->setUser($user), plainTextKey: $plainTextKey); + } + + public function countActiveKeys(int $userId, int $accountId): int + { + return $this->apiKeyRepository->countWhere([ + PersonalAccessTokenDomainObject::TOKENABLE_ID => $userId, + PersonalAccessTokenDomainObject::ACCOUNT_ID => $accountId, + static fn ($query) => $query + ->whereNull(PersonalAccessTokenDomainObject::EXPIRES_AT) + ->orWhere(PersonalAccessTokenDomainObject::EXPIRES_AT, '>', now()), + ]); + } + + public function revokeAllForMember(int $userId, int $accountId): void + { + $this->apiKeyRepository->deleteWhere([ + PersonalAccessTokenDomainObject::TOKENABLE_ID => $userId, + PersonalAccessTokenDomainObject::ACCOUNT_ID => $accountId, + ]); + } + + public function isUsableBy(int $userId, int $accountId): bool + { + return $this->accountUserRepository->findFirstWhere([ + 'user_id' => $userId, + 'account_id' => $accountId, + 'status' => UserStatus::ACTIVE->name, + ]) !== null; + } + + private function generateKey(): string + { + $entropy = Str::random(40); + + return $this->config->get('sanctum.token_prefix').$entropy.hash('crc32b', $entropy); + } +} diff --git a/backend/app/Services/Domain/Auth/ApiKey/DTO/CreatedApiKeyDTO.php b/backend/app/Services/Domain/Auth/ApiKey/DTO/CreatedApiKeyDTO.php new file mode 100644 index 0000000000..8455ff5eb7 --- /dev/null +++ b/backend/app/Services/Domain/Auth/ApiKey/DTO/CreatedApiKeyDTO.php @@ -0,0 +1,14 @@ +authManager->payload(); - } catch (JWTException) { - return null; + if ($apiKey = $this->currentApiKey()) { + return (int) $apiKey->getAttribute('account_id'); } - return $payload->get('account_id'); + $accountId = $this->sessionClaim('account_id'); + + return $accountId === null ? null : (int) $accountId; } public function getAuthenticatedTokenKey(): ?string { + if ($apiKey = $this->currentApiKey()) { + return 'api-key:'.$apiKey->getKey(); + } + try { $token = $this->authManager->guard()->getToken(); } catch (Throwable) { @@ -54,6 +58,48 @@ public function getAuthenticatedUserId(): ?int return (int) $this->authManager->id(); } + public function isApiKeyRequest(): bool + { + return $this->currentApiKey() !== null; + } + + public function apiKeyCanWrite(): bool + { + $apiKey = $this->currentApiKey(); + + return $apiKey === null || $apiKey->can(ApiKeyAccess::WRITE_ABILITY); + } + + public function isImpersonating(): bool + { + return (bool) $this->sessionClaim('is_impersonating', false); + } + + public function getImpersonatorId(): ?int + { + if (! $this->isImpersonating()) { + return null; + } + + $impersonatorId = $this->sessionClaim('impersonator_id'); + + return $impersonatorId === null ? null : (int) $impersonatorId; + } + + public function getSessionTokenId(): ?string + { + $tokenId = $this->sessionClaim('jti'); + + return is_string($tokenId) ? $tokenId : null; + } + + public function logout(): void + { + if (! $this->isApiKeyRequest()) { + $this->authManager->logout(); + } + } + public function getUser(): UserDomainObject|DomainObjectInterface|null { /** @var User $user */ @@ -72,4 +118,30 @@ public function getUser(): UserDomainObject|DomainObjectInterface|null return null; } + + private function currentApiKey(): ?PersonalAccessToken + { + $user = $this->authManager->user(); + + if (! $user instanceof User) { + return null; + } + + $token = $user->currentAccessToken(); + + return $token instanceof PersonalAccessToken ? $token : null; + } + + private function sessionClaim(string $claim, mixed $default = null): mixed + { + if (! $this->authManager->check() || $this->isApiKeyRequest()) { + return $default; + } + + try { + return $this->authManager->payload()->get($claim) ?? $default; + } catch (JWTException) { + return $default; + } + } } diff --git a/backend/app/Services/Domain/Auth/Sso/Enterprise/AccountSsoConnectionService.php b/backend/app/Services/Domain/Auth/Sso/Enterprise/AccountSsoConnectionService.php index 52ee409eac..60505413e8 100644 --- a/backend/app/Services/Domain/Auth/Sso/Enterprise/AccountSsoConnectionService.php +++ b/backend/app/Services/Domain/Auth/Sso/Enterprise/AccountSsoConnectionService.php @@ -83,7 +83,7 @@ public function update(AccountSsoConnectionDomainObject $connection, AccountSsoC 'last_tested_at' => null, ]; - $this->sessionRevocationService->revokeAllForUsers($this->identityUserIds($connection)); + $this->sessionRevocationService->revokeAllForUsers($this->identityUserIds($connection), $connection->getAccountId()); } if ($secret !== null) { @@ -121,7 +121,7 @@ public function deactivate(AccountSsoConnectionDomainObject $connection): Accoun 'status' => AccountSsoConnectionStatus::INACTIVE->value, ]); - $this->sessionRevocationService->revokeAllForUsers($this->identityUserIds($connection)); + $this->sessionRevocationService->revokeAllForUsers($this->identityUserIds($connection), $connection->getAccountId()); $this->logger->info('SSO connection deactivated', [ 'account_id' => $connection->getAccountId(), @@ -140,7 +140,7 @@ public function delete(AccountSsoConnectionDomainObject $connection): void throw new AccountSsoConfigurationException(__('Stop requiring single sign-on before deleting the connection.')); } - $this->sessionRevocationService->revokeAllForUsers($this->identityUserIds($connection)); + $this->sessionRevocationService->revokeAllForUsers($this->identityUserIds($connection), $connection->getAccountId()); $this->userIdentityRepository->deleteWhere(['account_sso_connection_id' => $connection->getId()]); $this->connectionRepository->deleteById($connection->getId()); diff --git a/backend/app/Services/Domain/Auth/Sso/Enterprise/SsoEnforcementService.php b/backend/app/Services/Domain/Auth/Sso/Enterprise/SsoEnforcementService.php index 9a832f785d..819778e5a3 100644 --- a/backend/app/Services/Domain/Auth/Sso/Enterprise/SsoEnforcementService.php +++ b/backend/app/Services/Domain/Auth/Sso/Enterprise/SsoEnforcementService.php @@ -76,6 +76,18 @@ public function assertSignInAllowed(UserDomainObject $user, int $accountId, Auth } } + /** + * @throws SsoRequiredException + */ + public function assertSignupAllowed(string $email): void + { + $domain = $this->domainService->findUsableDomainForEmail($email); + + if ($domain !== null && $this->findConnection($domain->getAccountId())?->isEnforcing() === true) { + throw new SsoRequiredException($email); + } + } + public function isConnectionSessionUsable(int $accountId, ?int $ssoConnectionId): bool { if ($ssoConnectionId === null) { @@ -239,7 +251,7 @@ public function enable(AccountSsoConnectionDomainObject $connection, array $exem } } - $this->sessionRevocationService->revokeAllForUsers($newlyEnforcedUserIds); + $this->sessionRevocationService->revokeAllForUsers($newlyEnforcedUserIds, $connection->getAccountId()); return $this->connectionRepository->updateFromArray($connection->getId(), ['enforce_sso' => true]); } @@ -269,7 +281,7 @@ public function setMemberEnforced(int $accountId, int $accountUserId, bool $enfo $this->accountUserRepository->updateWhere(['sso_enforced' => $enforced], ['id' => $accountUserId]); if ($enforced && ! $accountUser->getSsoEnforced() && $this->findConnection($accountId)?->isEnforcing()) { - $this->sessionRevocationService->revokeAll($accountUser->getUserId()); + $this->sessionRevocationService->revokeAll($accountUser->getUserId(), $accountId); } } diff --git a/backend/app/Services/Domain/Auth/UserSessionRevocationService.php b/backend/app/Services/Domain/Auth/UserSessionRevocationService.php index 820442e87e..d2d5795589 100644 --- a/backend/app/Services/Domain/Auth/UserSessionRevocationService.php +++ b/backend/app/Services/Domain/Auth/UserSessionRevocationService.php @@ -3,35 +3,42 @@ namespace HiEvents\Services\Domain\Auth; use Carbon\Carbon; +use HiEvents\DomainObjects\PersonalAccessTokenDomainObject; +use HiEvents\Repository\Interfaces\PersonalAccessTokenRepositoryInterface; use HiEvents\Repository\Interfaces\UserRepositoryInterface; class UserSessionRevocationService { public function __construct( private readonly UserRepositoryInterface $userRepository, + private readonly PersonalAccessTokenRepositoryInterface $apiKeyRepository, ) {} - public function revokeAll(int $userId): void + public function revokeAll(int $userId, ?int $apiKeyAccountId = null): void { - $this->userRepository->updateWhere( - attributes: ['tokens_valid_after' => now()->toDateTimeString()], - where: ['id' => $userId], - ); + $this->revokeAllForUsers([$userId], $apiKeyAccountId); } /** * @param int[] $userIds */ - public function revokeAllForUsers(array $userIds): void + public function revokeAllForUsers(array $userIds, ?int $apiKeyAccountId = null): void { if ($userIds === []) { return; } + $userIds = array_values(array_unique($userIds)); + $this->userRepository->updateWhere( attributes: ['tokens_valid_after' => now()->toDateTimeString()], - where: [['id', 'in', array_values(array_unique($userIds))]], + where: [['id', 'in', $userIds]], ); + + $this->apiKeyRepository->deleteWhere(array_filter([ + [PersonalAccessTokenDomainObject::TOKENABLE_ID, 'in', $userIds], + $apiKeyAccountId === null ? null : [PersonalAccessTokenDomainObject::ACCOUNT_ID, '=', $apiKeyAccountId], + ])); } public function isRevoked(?string $tokensValidAfter, int $issuedAt): bool diff --git a/backend/app/Services/Infrastructure/Authorization/IsAuthorizedService.php b/backend/app/Services/Infrastructure/Authorization/IsAuthorizedService.php index b16096b5a5..7515e6076a 100644 --- a/backend/app/Services/Infrastructure/Authorization/IsAuthorizedService.php +++ b/backend/app/Services/Infrastructure/Authorization/IsAuthorizedService.php @@ -26,7 +26,7 @@ use HiEvents\Repository\Interfaces\UserRepositoryInterface; use HiEvents\Services\Domain\Auth\Access\AccountUserAccess; use HiEvents\Services\Domain\Auth\Access\AccountUserAccessService; -use Illuminate\Auth\AuthManager; +use HiEvents\Services\Domain\Auth\AuthUserService; use Illuminate\Foundation\Application; readonly class IsAuthorizedService @@ -35,7 +35,7 @@ public function __construct( private Application $app, private AccountUserRepositoryInterface $accountUserRepository, private AccountUserAccessService $accessService, - private AuthManager $auth, + private AuthUserService $authUserService, ) {} public function validateSuperAdmin(UserDomainObject $authUser): void @@ -216,7 +216,7 @@ private function validateTax(int $taxOrFeeId, int $authAccountId): bool private function validateUserStatus(UserDomainObject $authUser): void { if ($authUser->getCurrentAccountUser()?->getStatus() !== UserStatus::ACTIVE->name) { - $this->auth->logout(); + $this->authUserService->logout(); throw new UnauthorizedException(__('Your account is not active.')); } } diff --git a/backend/config/app.php b/backend/config/app.php index c8ef9a73a5..bcb683497e 100644 --- a/backend/config/app.php +++ b/backend/config/app.php @@ -75,6 +75,7 @@ 'account_users' => '/settings/team', 'account_settings' => '/settings/account', 'profile_security' => '/settings/security', + 'api_keys' => '/settings/api-keys', 'event_dashboard' => '/manage/event/%d/dashboard', 'welcome' => '/welcome', 'lifecycle_email_unsubscribe' => '/emails/unsubscribe/%s', diff --git a/backend/config/auth.php b/backend/config/auth.php index 35c15d86b3..ae31880ef3 100644 --- a/backend/config/auth.php +++ b/backend/config/auth.php @@ -46,6 +46,11 @@ 'driver' => 'jwt', 'provider' => 'users', ], + + 'sanctum' => [ + 'driver' => 'sanctum', + 'provider' => 'users', + ], ], /* diff --git a/backend/config/sanctum.php b/backend/config/sanctum.php index e7f97fc45a..b9699aca22 100644 --- a/backend/config/sanctum.php +++ b/backend/config/sanctum.php @@ -1,69 +1,15 @@ false, - /* - |-------------------------------------------------------------------------- - | Stateful Domains - |-------------------------------------------------------------------------- - | - | Requests from the following domains / hosts will receive stateful API - | authentication cookies. Typically, these should include your local - | and production domains which access your API via a frontend SPA. - | - */ - - 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( - '%s%s', - 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', - Sanctum::currentApplicationUrlWithPort() - ))), - - /* - |-------------------------------------------------------------------------- - | Sanctum Guards - |-------------------------------------------------------------------------- - | - | This array contains the authentication guards that will be checked when - | Sanctum is trying to authenticate a request. If none of these guards - | are able to authenticate the request, Sanctum will use the bearer - | token that's present on an incoming request for authentication. - | - */ + 'stateful' => [], - 'guard' => ['web'], - - /* - |-------------------------------------------------------------------------- - | Expiration Minutes - |-------------------------------------------------------------------------- - | - | This value controls the number of minutes until an issued token will be - | considered expired. If this value is null, personal access tokens do - | not expire. This won't tweak the lifetime of first-party sessions. - | - */ + 'guard' => [], 'expiration' => null, - /* - |-------------------------------------------------------------------------- - | Sanctum Middleware - |-------------------------------------------------------------------------- - | - | When authenticating your first-party SPA with Sanctum you may need to - | customize some of the middleware Sanctum uses while processing the - | request. You may change the middleware listed below as required. - | - */ - - 'middleware' => [ - 'verify_csrf_token' => PreventRequestForgery::class, - 'encrypt_cookies' => EncryptCookies::class, - ], + 'token_prefix' => 'hie_', + 'last_used_at' => false, ]; diff --git a/backend/config/scramble.php b/backend/config/scramble.php index a8d0eca1f1..dec1c16bdb 100644 --- a/backend/config/scramble.php +++ b/backend/config/scramble.php @@ -24,7 +24,7 @@ * Multiple includes or wildcards → server defaults to / and paths stay full (/api/users). * Override with `servers`, or use Scramble::registerApi() for separate bases. */ - 'api_path' => 'api', + 'api_path' => '', /* * Your API domain. By default, app domain is used. This is also a part of the default API routes @@ -61,7 +61,27 @@ ## Authentication -Management endpoints require a bearer token (JWT) obtained from `POST /auth/login`. Endpoints under `/public/*` — event pages, checkout, ticket lookup, check-in — require no authentication and are marked *(public)*. +Management endpoints require a bearer token in the `Authorization` header. Endpoints under `/public/*` — event pages, checkout, ticket lookup, check-in — require no authentication and are marked *(public)*. + +### API keys + +For integrations, create an API key under **Settings → API keys** and send it with every request: + +```bash +curl https://your-hi-events-host/api/events \ + -H "Authorization: Bearer hie_…" \ + -H "Accept: application/json" +``` + +- A key acts as the person who created it, in the one account it was created in, with that person's role, permissions and event access. It never has more access than its owner, and changes to the owner's role apply straight away. +- **Read and write** keys can call every endpoint the owner can. **Read-only** keys can call `GET` endpoints and exports; other requests return `403` with `error_code: API_KEY_READ_ONLY`. +- Keys can't sign in, or manage the user's profile, sign-in security, team, API keys, payouts or account settings. Those endpoints return `403` with `error_code: API_KEY_NOT_ALLOWED`. +- An expired, revoked or unknown key returns `401`. A key is revoked when its owner revokes it, when an admin revokes it, when the owner is deactivated or leaves the account, and when the owner's password is reset or their single sign-on changes. +- The full key is shown once, when it's created. Hi.Events stores only a hash of it. + +### Session tokens + +The Hi.Events web app uses a JWT obtained from `POST /auth/login`. It works on every management endpoint, including the ones API keys can't use. ### Two-factor authentication @@ -71,9 +91,33 @@ If an account requires two-factor authentication and the user has not set it up, authenticated endpoints return `403` with `error_code: TWO_FACTOR_SETUP_REQUIRED` until they do. `GET /users/me`, `GET /accounts`, the `/auth/*` endpoints and the `/users/me/two-factor/*` setup endpoints stay available. +## Errors + +Errors return JSON with a `message`. Validation errors (`422`) also include an `errors` object keyed by field. Access errors include an `error_code`: + +| Status | `error_code` | Meaning | +| --- | --- | --- | +| `401` | — | Missing, invalid or expired token, or an invalid, expired or revoked API key | +| `401` | `SESSION_REVOKED` | The session token was signed out everywhere, for example after a password reset | +| `401` | `SSO_REQUIRED` | The user's organization requires single sign-on; sign in again through it | +| `401` | `SSO_SESSION_EXPIRED` | A session started through single sign-on can't be refreshed; sign in again | +| `403` | `API_KEY_READ_ONLY` | A read-only API key tried to make a change | +| `403` | `API_KEY_NOT_ALLOWED` | The endpoint can't be used with an API key | +| `403` | `READ_ONLY_ROLE` | The user has view-only access to the account | +| `403` | `PERMISSION_REQUIRED` | The user's role lacks a permission the endpoint needs (for example refunds or financial data) | +| `403` | `ACCESS_RESTRICTED` | The user only has access to some organizers or events, and this isn't one of them | +| `403` | `TWO_FACTOR_SETUP_REQUIRED` | The account requires two-factor authentication and the user hasn't set it up | +| `403` | `ACCOUNT_PENDING_DELETION` | The account is scheduled for deletion | +| `403` | `FEATURE_UNAVAILABLE` | The feature isn't available on this installation or plan | +| `429` | — | Too many requests; wait for the number of seconds in the `Retry-After` header | + +## Rate limits + +Management endpoints are rate limited per user, shared between the web app and all of that user's API keys. The default is 180 requests a minute; self-hosted installations can change it with `APP_API_RATE_LIMIT_PER_MINUTE`. + ## Stability -This API is currently **unversioned and likely to change**. Endpoints, request fields, and response schemas may change between releases without notice, so pin your integration to a specific Hi.Events release and review the changelog when upgrading. +The API isn't versioned yet. Endpoints that accept API keys are the integration surface, and we keep them backward compatible: new endpoints, optional request fields and response fields can be added at any time, so ignore fields you don't recognise. Removing or renaming an endpoint or field, or changing its type or meaning, is a breaking change; it's avoided where possible and always called out in the release notes. Endpoints marked *Not available to API keys* serve the Hi.Events web app and may change without notice. ## Outgoing webhooks diff --git a/backend/database/migrations/2026_10_17_200000_add_api_key_columns_to_personal_access_tokens.php b/backend/database/migrations/2026_10_17_200000_add_api_key_columns_to_personal_access_tokens.php new file mode 100644 index 0000000000..d57b312ae2 --- /dev/null +++ b/backend/database/migrations/2026_10_17_200000_add_api_key_columns_to_personal_access_tokens.php @@ -0,0 +1,33 @@ +delete(); + + Schema::table('personal_access_tokens', function (Blueprint $table) { + $table->foreignId('account_id')->constrained('accounts'); + $table->string('token_hint', 8); + $table->timestamp('expiry_reminder_sent_at')->nullable(); + + $table->index(['account_id', 'tokenable_id']); + $table->index('expires_at'); + }); + } + + public function down(): void + { + Schema::table('personal_access_tokens', function (Blueprint $table) { + $table->dropIndex(['account_id', 'tokenable_id']); + $table->dropIndex(['expires_at']); + $table->dropConstrainedForeignId('account_id'); + $table->dropColumn(['token_hint', 'expiry_reminder_sent_at']); + }); + } +}; diff --git a/backend/lang/de.json b/backend/lang/de.json index 2235d1407e..b643b7b893 100644 --- a/backend/lang/de.json +++ b/backend/lang/de.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Wir konnten Ihren Identity Provider nicht erreichen. Prüfen Sie die Verbindungsdaten und versuchen Sie es erneut.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Sie sind Inhaber eines Kontos, für das Single Sign-On verpflichtend ist. Die Zwei-Faktor-Authentifizierung schützt Ihre Ersatzanmeldung und kann daher nicht deaktiviert werden.", "Your single sign-on settings were changed": "Ihre Single-Sign-On-Einstellungen wurden geändert", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Für Ihr :appName-Konto wurde ein neuer API-Schlüssel erstellt. Jeder mit diesem Schlüssel kann die API mit Ihrem Zugriff auf :accountName nutzen.", + "A new API key was created on your account": "Für Ihr Konto wurde ein neuer API-Schlüssel erstellt", + "API key not found": "API-Schlüssel nicht gefunden", + "API keys can't be used for this request. Sign in to do this instead.": "Für diese Anfrage können keine API-Schlüssel verwendet werden. Melden Sie sich stattdessen an.", + "API keys cannot be created while impersonating a user.": "API-Schlüssel können nicht erstellt werden, während Sie als ein anderer Benutzer angemeldet sind.", + "Access": "Zugriff", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Ein Administrator von :accountName hat Ihren API-Schlüssel „:keyName“ widerrufen. Integrationen, die ihn verwenden, können sich nicht mehr verbinden.", + "Choose one of the available expiry options.": "Wählen Sie eine der verfügbaren Ablaufoptionen.", + "Confirm your email address before creating an API key.": "Bestätigen Sie Ihre E-Mail-Adresse, bevor Sie einen API-Schlüssel erstellen.", + "Create a new key": "Neuen Schlüssel erstellen", + "Expires": "Läuft ab", + "If you did not create this key, revoke it now, change your password and contact support.": "Wenn Sie diesen Schlüssel nicht erstellt haben, widerrufen Sie ihn sofort, ändern Sie Ihr Passwort und wenden Sie sich an den Support.", + "If you still need API access, create a new key and update your integration.": "Wenn Sie weiterhin API-Zugriff benötigen, erstellen Sie einen neuen Schlüssel und aktualisieren Sie Ihre Integration.", + "Manage API keys": "API-Schlüssel verwalten", + "Never": "Nie", + "One of your API keys was revoked": "Einer Ihrer API-Schlüssel wurde widerrufen", + "Read and write": "Lesen und Schreiben", + "Read-only": "Nur Lesen", + "Review API keys": "API-Schlüssel prüfen", + "This API key is invalid, has expired or has been revoked.": "Dieser API-Schlüssel ist ungültig, abgelaufen oder wurde widerrufen.", + "This API key is read-only. Create a key with read and write access to make changes.": "Dieser API-Schlüssel hat nur Lesezugriff. Erstellen Sie einen Schlüssel mit Lese- und Schreibzugriff, um Änderungen vorzunehmen.", + "To avoid an interruption, create a new key and update your integration before then.": "Um eine Unterbrechung zu vermeiden, erstellen Sie vorher einen neuen Schlüssel und aktualisieren Sie Ihre Integration.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Verwenden Sie im Namen Buchstaben, Zahlen, Leerzeichen und einfache Satzzeichen.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Sie können bis zu :max aktive API-Schlüssel haben. Widerrufen Sie einen, den Sie nicht mehr verwenden, und versuchen Sie es erneut.", + "You have view-only access, so you can only create read-only API keys.": "Sie haben nur Lesezugriff und können daher nur API-Schlüssel mit Lesezugriff erstellen.", + "Your API key \":keyName\" expires soon": "Ihr API-Schlüssel „:keyName“ läuft bald ab", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Ihr API-Schlüssel „:keyName“ für :accountName läuft am :date ab. Danach können sich Integrationen, die ihn verwenden, nicht mehr verbinden.", "Stop requiring single sign-on before removing a verified domain.": "Beenden Sie die Single-Sign-On-Pflicht, bevor Sie eine verifizierte Domain entfernen.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google-Tag-Manager-Container können nur im Dashboard hinzugefügt werden, nicht mit einem API-Schlüssel.", "You signed in without single sign-on": "Sie haben sich ohne Single Sign-On angemeldet", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Falls Sie das nicht waren, ändern Sie sofort Ihr Passwort und richten Sie die Zwei-Faktor-Authentifizierung mit einer neuen Authenticator-App erneut ein.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Diese Person besitzt ein Konto, das Single Sign-On verlangt. Die Zwei-Faktor-Authentifizierung ist ihre Notfall-Anmeldung. Bitten Sie sie daher, die Single-Sign-On-Pflicht zuerst zu beenden." diff --git a/backend/lang/el.json b/backend/lang/el.json index 47646dc203..5452728ff4 100644 --- a/backend/lang/el.json +++ b/backend/lang/el.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Δεν ήταν δυνατή η επικοινωνία με τον πάροχο ταυτότητάς σας. Ελέγξτε τα στοιχεία σύνδεσης και δοκιμάστε ξανά.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Είστε κάτοχος λογαριασμού που απαιτεί ενιαία σύνδεση. Ο έλεγχος ταυτότητας δύο παραγόντων προστατεύει την εφεδρική σύνδεσή σας, επομένως δεν μπορεί να απενεργοποιηθεί.", "Your single sign-on settings were changed": "Οι ρυθμίσεις ενιαίας σύνδεσης άλλαξαν", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Δημιουργήθηκε νέο κλειδί API στον λογαριασμό σας στο :appName. Όποιος έχει αυτό το κλειδί μπορεί να χρησιμοποιήσει το API με την πρόσβασή σας στο :accountName.", + "A new API key was created on your account": "Δημιουργήθηκε νέο κλειδί API στον λογαριασμό σας", + "API key not found": "Το κλειδί API δεν βρέθηκε", + "API keys can't be used for this request. Sign in to do this instead.": "Τα κλειδιά API δεν μπορούν να χρησιμοποιηθούν για αυτό το αίτημα. Συνδεθείτε για να το κάνετε.", + "API keys cannot be created while impersonating a user.": "Δεν μπορείτε να δημιουργήσετε κλειδιά API ενώ ενεργείτε ως άλλος χρήστης.", + "Access": "Πρόσβαση", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Ένας διαχειριστής του :accountName ανακάλεσε το κλειδί API σας «:keyName». Οι ενσωματώσεις που το χρησιμοποιούν δεν μπορούν πλέον να συνδεθούν.", + "Choose one of the available expiry options.": "Επιλέξτε μία από τις διαθέσιμες επιλογές λήξης.", + "Confirm your email address before creating an API key.": "Επιβεβαιώστε τη διεύθυνση email σας πριν δημιουργήσετε κλειδί API.", + "Create a new key": "Δημιουργία νέου κλειδιού", + "Expires": "Λήξη", + "If you did not create this key, revoke it now, change your password and contact support.": "Αν δεν δημιουργήσατε εσείς αυτό το κλειδί, ανακαλέστε το τώρα, αλλάξτε τον κωδικό πρόσβασής σας και επικοινωνήστε με την υποστήριξη.", + "If you still need API access, create a new key and update your integration.": "Αν χρειάζεστε ακόμη πρόσβαση στο API, δημιουργήστε νέο κλειδί και ενημερώστε την ενσωμάτωσή σας.", + "Manage API keys": "Διαχείριση κλειδιών API", + "Never": "Ποτέ", + "One of your API keys was revoked": "Ένα από τα κλειδιά API σας ανακλήθηκε", + "Read and write": "Ανάγνωση και εγγραφή", + "Read-only": "Μόνο ανάγνωση", + "Review API keys": "Έλεγχος κλειδιών API", + "This API key is invalid, has expired or has been revoked.": "Αυτό το κλειδί API δεν είναι έγκυρο, έχει λήξει ή έχει ανακληθεί.", + "This API key is read-only. Create a key with read and write access to make changes.": "Αυτό το κλειδί API είναι μόνο ανάγνωσης. Δημιουργήστε ένα κλειδί με πρόσβαση ανάγνωσης και εγγραφής για να κάνετε αλλαγές.", + "To avoid an interruption, create a new key and update your integration before then.": "Για να αποφύγετε διακοπή, δημιουργήστε νέο κλειδί και ενημερώστε την ενσωμάτωσή σας πριν από τότε.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Χρησιμοποιήστε γράμματα, αριθμούς, κενά και βασικά σημεία στίξης στο όνομα.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Μπορείτε να έχετε έως :max ενεργά κλειδιά API. Ανακαλέστε ένα που δεν χρησιμοποιείτε πλέον και δοκιμάστε ξανά.", + "You have view-only access, so you can only create read-only API keys.": "Έχετε πρόσβαση μόνο προβολής, επομένως μπορείτε να δημιουργήσετε μόνο κλειδιά API με δικαίωμα ανάγνωσης.", + "Your API key \":keyName\" expires soon": "Το κλειδί API σας «:keyName» λήγει σύντομα", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Το κλειδί API σας «:keyName» για το :accountName λήγει στις :date. Μετά από αυτό, οι ενσωματώσεις που το χρησιμοποιούν δεν θα μπορούν πλέον να συνδεθούν.", "Stop requiring single sign-on before removing a verified domain.": "Σταματήστε την υποχρεωτική σύνδεση μέσω SSO πριν αφαιρέσετε έναν επαληθευμένο τομέα.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Τα κοντέινερ του Google Tag Manager προστίθενται μόνο από τον πίνακα ελέγχου, όχι με κλειδί API.", "You signed in without single sign-on": "Συνδεθήκατε χωρίς ενιαία σύνδεση (SSO)", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Αν δεν ήσασταν εσείς, αλλάξτε αμέσως τον κωδικό πρόσβασής σας και ρυθμίστε ξανά τον έλεγχο ταυτότητας δύο παραγόντων με νέα εφαρμογή επαλήθευσης.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Αυτός ο χρήστης είναι κάτοχος λογαριασμού που απαιτεί ενιαία σύνδεση. Ο έλεγχος ταυτότητας δύο παραγόντων είναι η εφεδρική του σύνδεση, οπότε ζητήστε του πρώτα να σταματήσει την υποχρεωτική ενιαία σύνδεση." diff --git a/backend/lang/es.json b/backend/lang/es.json index deee6ad5f3..005c1c6a67 100644 --- a/backend/lang/es.json +++ b/backend/lang/es.json @@ -1186,7 +1186,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "No pudimos conectar con tu proveedor de identidad. Revisa los datos de conexión e inténtalo de nuevo.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Eres propietario de una cuenta que exige el inicio de sesión único. La autenticación de dos factores protege tu acceso de respaldo, por lo que no se puede desactivar.", "Your single sign-on settings were changed": "Se han cambiado los ajustes de inicio de sesión único", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Se ha creado una nueva clave de API en tu cuenta de :appName. Cualquiera que tenga esta clave puede usar la API con tu acceso a :accountName.", + "A new API key was created on your account": "Se ha creado una nueva clave de API en tu cuenta", + "API key not found": "Clave de API no encontrada", + "API keys can't be used for this request. Sign in to do this instead.": "Las claves de API no se pueden usar para esta solicitud. Inicia sesión para hacerlo.", + "API keys cannot be created while impersonating a user.": "No se pueden crear claves de API mientras suplantas a otro usuario.", + "Access": "Acceso", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Un administrador de :accountName ha revocado tu clave de API «:keyName». Las integraciones que la usan ya no pueden conectarse.", + "Choose one of the available expiry options.": "Elige una de las opciones de caducidad disponibles.", + "Confirm your email address before creating an API key.": "Confirma tu dirección de correo electrónico antes de crear una clave de API.", + "Create a new key": "Crear una nueva clave", + "Expires": "Caduca", + "If you did not create this key, revoke it now, change your password and contact support.": "Si no creaste esta clave, revócala ahora, cambia tu contraseña y contacta con el soporte.", + "If you still need API access, create a new key and update your integration.": "Si todavía necesitas acceso a la API, crea una nueva clave y actualiza tu integración.", + "Manage API keys": "Gestionar claves de API", + "Never": "Nunca", + "One of your API keys was revoked": "Se ha revocado una de tus claves de API", + "Read and write": "Lectura y escritura", + "Read-only": "Solo lectura", + "Review API keys": "Revisar claves de API", + "This API key is invalid, has expired or has been revoked.": "Esta clave de API no es válida, ha caducado o ha sido revocada.", + "This API key is read-only. Create a key with read and write access to make changes.": "Esta clave de API es de solo lectura. Crea una clave con acceso de lectura y escritura para hacer cambios.", + "To avoid an interruption, create a new key and update your integration before then.": "Para evitar interrupciones, crea una nueva clave y actualiza tu integración antes de esa fecha.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Usa letras, números, espacios y signos de puntuación básicos en el nombre.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Puedes tener hasta :max claves de API activas. Revoca una que ya no uses e inténtalo de nuevo.", + "You have view-only access, so you can only create read-only API keys.": "Tienes acceso de solo lectura, así que solo puedes crear claves de API de solo lectura.", + "Your API key \":keyName\" expires soon": "Tu clave de API «:keyName» caduca pronto", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Tu clave de API «:keyName» para :accountName caduca el :date. Después, las integraciones que la usan ya no podrán conectarse.", "Stop requiring single sign-on before removing a verified domain.": "Deja de exigir el inicio de sesión único antes de eliminar un dominio verificado.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Los contenedores de Google Tag Manager solo se pueden añadir desde el panel, no con una clave de API.", "You signed in without single sign-on": "Has iniciado sesión sin el inicio de sesión único", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Si no fuiste tú, cambia tu contraseña y vuelve a configurar la autenticación en dos pasos con una nueva aplicación de autenticación de inmediato.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Este usuario es propietario de una cuenta que exige el inicio de sesión único. La autenticación en dos pasos es su acceso de respaldo, así que pídele primero que deje de exigir el inicio de sesión único." diff --git a/backend/lang/fi.json b/backend/lang/fi.json index 33f329c28f..d110375cbd 100644 --- a/backend/lang/fi.json +++ b/backend/lang/fi.json @@ -1334,7 +1334,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Identiteetintarjoajaasi ei tavoitettu. Tarkista yhteyden tiedot ja yritä uudelleen.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Omistat tilin, joka vaatii kertakirjautumisen. Kaksivaiheinen tunnistautuminen suojaa varakirjautumistasi, joten sitä ei voi poistaa käytöstä.", "Your single sign-on settings were changed": "Kertakirjautumisen asetuksia muutettiin", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": ":appName-tilillesi luotiin uusi API-avain. Kuka tahansa, jolla on tämä avain, voi käyttää API-rajapintaa sinun käyttöoikeuksillasi tiliin :accountName.", + "A new API key was created on your account": "Tilillesi luotiin uusi API-avain", + "API key not found": "API-avainta ei löytynyt", + "API keys can't be used for this request. Sign in to do this instead.": "API-avaimia ei voi käyttää tähän pyyntöön. Kirjaudu sisään tehdäksesi tämän.", + "API keys cannot be created while impersonating a user.": "API-avaimia ei voi luoda, kun toimit toisena käyttäjänä.", + "Access": "Käyttöoikeus", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Tilin :accountName ylläpitäjä mitätöi API-avaimesi ”:keyName”. Sitä käyttävät integraatiot eivät voi enää muodostaa yhteyttä.", + "Choose one of the available expiry options.": "Valitse jokin käytettävissä olevista vanhenemisvaihtoehdoista.", + "Confirm your email address before creating an API key.": "Vahvista sähköpostiosoitteesi ennen API-avaimen luomista.", + "Create a new key": "Luo uusi avain", + "Expires": "Vanhenee", + "If you did not create this key, revoke it now, change your password and contact support.": "Jos et luonut tätä avainta, mitätöi se nyt, vaihda salasanasi ja ota yhteyttä tukeen.", + "If you still need API access, create a new key and update your integration.": "Jos tarvitset yhä API-pääsyä, luo uusi avain ja päivitä integraatiosi.", + "Manage API keys": "Hallitse API-avaimia", + "Never": "Ei koskaan", + "One of your API keys was revoked": "Yksi API-avaimistasi mitätöitiin", + "Read and write": "Luku ja kirjoitus", + "Read-only": "Vain luku", + "Review API keys": "Tarkista API-avaimet", + "This API key is invalid, has expired or has been revoked.": "Tämä API-avain on virheellinen, vanhentunut tai mitätöity.", + "This API key is read-only. Create a key with read and write access to make changes.": "Tämä API-avain on vain luku -avain. Luo avain, jolla on luku- ja kirjoitusoikeus, tehdäksesi muutoksia.", + "To avoid an interruption, create a new key and update your integration before then.": "Välttääksesi katkoksen luo uusi avain ja päivitä integraatiosi ennen sitä.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Käytä nimessä kirjaimia, numeroita, välilyöntejä ja tavallisia välimerkkejä.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Sinulla voi olla enintään :max aktiivista API-avainta. Mitätöi jokin, jota et enää käytä, ja yritä uudelleen.", + "You have view-only access, so you can only create read-only API keys.": "Sinulla on vain lukuoikeus, joten voit luoda vain API-avaimia, joilla on lukuoikeus.", + "Your API key \":keyName\" expires soon": "API-avaimesi ”:keyName” vanhenee pian", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "API-avaimesi ”:keyName” tilille :accountName vanhenee :date. Sen jälkeen sitä käyttävät integraatiot eivät voi enää muodostaa yhteyttä.", "Stop requiring single sign-on before removing a verified domain.": "Lopeta kertakirjautumisen vaatiminen ennen kuin poistat vahvistetun verkkotunnuksen.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google Tag Manager -säilöjä voi lisätä vain hallintapaneelista, ei API-avaimella.", "You signed in without single sign-on": "Kirjauduit sisään ilman kertakirjautumista", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Jos tämä et ollut sinä, vaihda salasanasi ja ota kaksivaiheinen tunnistautuminen uudelleen käyttöön uudella todennussovelluksella heti.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Tämä käyttäjä omistaa tilin, joka vaatii kertakirjautumisen. Kaksivaiheinen tunnistautuminen on hänen varakirjautumisensa, joten pyydä häntä ensin lopettamaan kertakirjautumisen vaatiminen." diff --git a/backend/lang/fr.json b/backend/lang/fr.json index 920dfcf07e..14f1faf93f 100644 --- a/backend/lang/fr.json +++ b/backend/lang/fr.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Nous n'avons pas pu joindre votre fournisseur d'identité. Vérifiez les informations de connexion et réessayez.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Vous possédez un compte qui exige l'authentification unique. L'authentification à deux facteurs protège votre connexion de secours et ne peut donc pas être désactivée.", "Your single sign-on settings were changed": "Les paramètres d'authentification unique de votre compte ont été modifiés", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Une nouvelle clé API a été créée sur votre compte :appName. Toute personne disposant de cette clé peut utiliser l'API avec vos accès à :accountName.", + "A new API key was created on your account": "Une nouvelle clé API a été créée sur votre compte", + "API key not found": "Clé API introuvable", + "API keys can't be used for this request. Sign in to do this instead.": "Les clés API ne peuvent pas être utilisées pour cette requête. Connectez-vous pour effectuer cette action.", + "API keys cannot be created while impersonating a user.": "Les clés API ne peuvent pas être créées lorsque vous êtes connecté en tant qu'un autre utilisateur.", + "Access": "Accès", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Un administrateur de :accountName a révoqué votre clé API « :keyName ». Les intégrations qui l'utilisent ne peuvent plus se connecter.", + "Choose one of the available expiry options.": "Choisissez l'une des options d'expiration disponibles.", + "Confirm your email address before creating an API key.": "Confirmez votre adresse e-mail avant de créer une clé API.", + "Create a new key": "Créer une nouvelle clé", + "Expires": "Expiration", + "If you did not create this key, revoke it now, change your password and contact support.": "Si vous n'avez pas créé cette clé, révoquez-la maintenant, changez votre mot de passe et contactez le support.", + "If you still need API access, create a new key and update your integration.": "Si vous avez toujours besoin d'un accès à l'API, créez une nouvelle clé et mettez à jour votre intégration.", + "Manage API keys": "Gérer les clés API", + "Never": "Jamais", + "One of your API keys was revoked": "L'une de vos clés API a été révoquée", + "Read and write": "Lecture et écriture", + "Read-only": "Lecture seule", + "Review API keys": "Vérifier les clés API", + "This API key is invalid, has expired or has been revoked.": "Cette clé API est invalide, a expiré ou a été révoquée.", + "This API key is read-only. Create a key with read and write access to make changes.": "Cette clé API est en lecture seule. Créez une clé avec un accès en lecture et écriture pour apporter des modifications.", + "To avoid an interruption, create a new key and update your integration before then.": "Pour éviter toute interruption, créez une nouvelle clé et mettez à jour votre intégration avant cette date.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Utilisez des lettres, des chiffres, des espaces et une ponctuation simple dans le nom.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Vous pouvez avoir jusqu'à :max clés API actives. Révoquez-en une que vous n'utilisez plus et réessayez.", + "You have view-only access, so you can only create read-only API keys.": "Vous avez un accès en lecture seule : vous ne pouvez donc créer que des clés API en lecture seule.", + "Your API key \":keyName\" expires soon": "Votre clé API « :keyName » expire bientôt", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Votre clé API « :keyName » pour :accountName expire le :date. Après cette date, les intégrations qui l'utilisent ne pourront plus se connecter.", "Stop requiring single sign-on before removing a verified domain.": "Cessez d'exiger l'authentification unique avant de supprimer un domaine vérifié.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Les conteneurs Google Tag Manager ne peuvent être ajoutés que depuis le tableau de bord, pas avec une clé API.", "You signed in without single sign-on": "Vous vous êtes connecté sans l'authentification unique", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Si ce n'était pas vous, changez immédiatement votre mot de passe et configurez à nouveau l'authentification à deux facteurs avec une nouvelle application d'authentification.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Cet utilisateur possède un compte qui exige l'authentification unique. L'authentification à deux facteurs est sa connexion de secours : demandez-lui d'abord de cesser d'exiger l'authentification unique." diff --git a/backend/lang/hu.json b/backend/lang/hu.json index 0af6e6c044..fd0dab1133 100644 --- a/backend/lang/hu.json +++ b/backend/lang/hu.json @@ -1151,7 +1151,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Nem sikerült elérnünk az identitásszolgáltatóját. Ellenőrizze a kapcsolat adatait, és próbálja újra.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Olyan fiók tulajdonosa, amelyhez egyszeri bejelentkezés szükséges. A kétlépcsős hitelesítés védi a tartalék bejelentkezését, ezért nem kapcsolható ki.", "Your single sign-on settings were changed": "Az egyszeri bejelentkezési beállítások megváltoztak", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Új API-kulcs jött létre a(z) :appName fiókjában. Bárki, akinél ez a kulcs van, az Ön hozzáférésével használhatja az API-t a(z) :accountName fiókban.", + "A new API key was created on your account": "Új API-kulcs jött létre a fiókjában", + "API key not found": "Az API-kulcs nem található", + "API keys can't be used for this request. Sign in to do this instead.": "Ehhez a kéréshez nem használhatók API-kulcsok. Ehhez inkább jelentkezzen be.", + "API keys cannot be created while impersonating a user.": "Más felhasználó megszemélyesítése közben nem hozhatók létre API-kulcsok.", + "Access": "Hozzáférés", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "A(z) :accountName egyik adminisztrátora visszavonta a(z) „:keyName” API-kulcsát. Az azt használó integrációk többé nem tudnak csatlakozni.", + "Choose one of the available expiry options.": "Válasszon a rendelkezésre álló lejárati lehetőségek közül.", + "Confirm your email address before creating an API key.": "API-kulcs létrehozása előtt erősítse meg e-mail-címét.", + "Create a new key": "Új kulcs létrehozása", + "Expires": "Lejárat", + "If you did not create this key, revoke it now, change your password and contact support.": "Ha nem Ön hozta létre ezt a kulcsot, vonja vissza most, változtassa meg jelszavát, és lépjen kapcsolatba az ügyfélszolgálattal.", + "If you still need API access, create a new key and update your integration.": "Ha továbbra is API-hozzáférésre van szüksége, hozzon létre egy új kulcsot, és frissítse az integrációját.", + "Manage API keys": "API-kulcsok kezelése", + "Never": "Soha", + "One of your API keys was revoked": "Az egyik API-kulcsát visszavonták", + "Read and write": "Olvasás és írás", + "Read-only": "Csak olvasás", + "Review API keys": "API-kulcsok áttekintése", + "This API key is invalid, has expired or has been revoked.": "Ez az API-kulcs érvénytelen, lejárt vagy visszavonták.", + "This API key is read-only. Create a key with read and write access to make changes.": "Ez az API-kulcs csak olvasható. Módosításokhoz hozzon létre olvasási és írási jogosultsággal rendelkező kulcsot.", + "To avoid an interruption, create a new key and update your integration before then.": "A megszakítás elkerülése érdekében addig hozzon létre egy új kulcsot, és frissítse az integrációját.", + "Use letters, numbers, spaces and basic punctuation in the name.": "A névben betűket, számokat, szóközöket és alapvető írásjeleket használjon.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Legfeljebb :max aktív API-kulcsa lehet. Vonjon vissza egyet, amelyet már nem használ, és próbálja újra.", + "You have view-only access, so you can only create read-only API keys.": "Csak megtekintési jogosultsága van, ezért kizárólag csak olvasható API-kulcsokat hozhat létre.", + "Your API key \":keyName\" expires soon": "A(z) „:keyName” API-kulcsa hamarosan lejár", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "A(z) :accountName fiókhoz tartozó „:keyName” API-kulcsa lejár ekkor: :date. Ezt követően az azt használó integrációk nem tudnak csatlakozni.", "Stop requiring single sign-on before removing a verified domain.": "Ellenőrzött domain eltávolítása előtt kapcsold ki az egyszeri bejelentkezés kötelezővé tételét.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google Tag Manager-tárolót csak az irányítópulton lehet hozzáadni, API-kulccsal nem.", "You signed in without single sign-on": "Egyszeri bejelentkezés nélkül jelentkeztél be", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Ha nem te voltál, azonnal változtasd meg a jelszavad, és állítsd be újra a kétlépcsős azonosítást egy új hitelesítő alkalmazással.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Ez a felhasználó egy olyan fiók tulajdonosa, amely egyszeri bejelentkezést követel meg. A kétlépcsős azonosítás a tartalék bejelentkezése, ezért először kérd meg, hogy kapcsolja ki az egyszeri bejelentkezés kötelezővé tételét." diff --git a/backend/lang/it.json b/backend/lang/it.json index c4d4c1ebb1..2340f72538 100644 --- a/backend/lang/it.json +++ b/backend/lang/it.json @@ -1147,7 +1147,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Non siamo riusciti a raggiungere il tuo identity provider. Controlla i dettagli di connessione e riprova.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Sei proprietario di un account che richiede il single sign-on. L'autenticazione a due fattori protegge il tuo accesso di riserva, quindi non può essere disattivata.", "Your single sign-on settings were changed": "Le impostazioni del single sign-on sono state modificate", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "È stata creata una nuova chiave API sul tuo account :appName. Chiunque abbia questa chiave può usare l'API con il tuo accesso a :accountName.", + "A new API key was created on your account": "È stata creata una nuova chiave API sul tuo account", + "API key not found": "Chiave API non trovata", + "API keys can't be used for this request. Sign in to do this instead.": "Le chiavi API non possono essere usate per questa richiesta. Accedi per farlo.", + "API keys cannot be created while impersonating a user.": "Non è possibile creare chiavi API mentre si impersona un altro utente.", + "Access": "Accesso", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Un amministratore di :accountName ha revocato la tua chiave API “:keyName”. Le integrazioni che la usano non possono più connettersi.", + "Choose one of the available expiry options.": "Scegli una delle opzioni di scadenza disponibili.", + "Confirm your email address before creating an API key.": "Conferma il tuo indirizzo email prima di creare una chiave API.", + "Create a new key": "Crea una nuova chiave", + "Expires": "Scadenza", + "If you did not create this key, revoke it now, change your password and contact support.": "Se non hai creato tu questa chiave, revocala subito, cambia la password e contatta l'assistenza.", + "If you still need API access, create a new key and update your integration.": "Se hai ancora bisogno dell'accesso API, crea una nuova chiave e aggiorna la tua integrazione.", + "Manage API keys": "Gestisci chiavi API", + "Never": "Mai", + "One of your API keys was revoked": "Una delle tue chiavi API è stata revocata", + "Read and write": "Lettura e scrittura", + "Read-only": "Sola lettura", + "Review API keys": "Controlla chiavi API", + "This API key is invalid, has expired or has been revoked.": "Questa chiave API non è valida, è scaduta o è stata revocata.", + "This API key is read-only. Create a key with read and write access to make changes.": "Questa chiave API è di sola lettura. Crea una chiave con accesso in lettura e scrittura per apportare modifiche.", + "To avoid an interruption, create a new key and update your integration before then.": "Per evitare interruzioni, crea una nuova chiave e aggiorna la tua integrazione prima di allora.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Usa lettere, numeri, spazi e punteggiatura di base nel nome.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Puoi avere fino a :max chiavi API attive. Revocane una che non usi più e riprova.", + "You have view-only access, so you can only create read-only API keys.": "Hai accesso in sola lettura, quindi puoi creare solo chiavi API di sola lettura.", + "Your API key \":keyName\" expires soon": "La tua chiave API “:keyName” scade a breve", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "La tua chiave API “:keyName” per :accountName scade il :date. Dopo tale data, le integrazioni che la usano non potranno più connettersi.", "Stop requiring single sign-on before removing a verified domain.": "Smetti di richiedere il single sign-on prima di rimuovere un dominio verificato.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "I contenitori di Google Tag Manager si possono aggiungere solo dalla dashboard, non con una chiave API.", "You signed in without single sign-on": "Hai effettuato l'accesso senza single sign-on", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Se non sei stato tu, cambia subito la password e configura di nuovo l'autenticazione a due fattori con una nuova app di autenticazione.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Questo utente è proprietario di un account che richiede il single sign-on. L'autenticazione a due fattori è il suo accesso di emergenza, quindi chiedigli prima di smettere di richiedere il single sign-on." diff --git a/backend/lang/ja.json b/backend/lang/ja.json index a4ab7110d2..8b77cb06cf 100644 --- a/backend/lang/ja.json +++ b/backend/lang/ja.json @@ -1334,7 +1334,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "IDプロバイダーに接続できませんでした。接続情報を確認して、もう一度お試しください。", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "シングルサインオンが必須のアカウントのオーナーです。二要素認証は予備のサインインを保護するため、オフにできません。", "Your single sign-on settings were changed": "シングルサインオンの設定が変更されました", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": ":appName アカウントで新しいAPIキーが作成されました。このキーを持つ人は誰でも、:accountName へのあなたのアクセス権でAPIを使用できます。", + "A new API key was created on your account": "アカウントで新しいAPIキーが作成されました", + "API key not found": "APIキーが見つかりません", + "API keys can't be used for this request. Sign in to do this instead.": "このリクエストにはAPIキーを使用できません。代わりにサインインして操作してください。", + "API keys cannot be created while impersonating a user.": "他のユーザーになりすましている間はAPIキーを作成できません。", + "Access": "アクセス", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": ":accountName の管理者があなたのAPIキー「:keyName」を無効化しました。このキーを使用している連携は接続できなくなります。", + "Choose one of the available expiry options.": "利用可能な有効期限のオプションから選択してください。", + "Confirm your email address before creating an API key.": "APIキーを作成する前に、メールアドレスを確認してください。", + "Create a new key": "新しいキーを作成", + "Expires": "有効期限", + "If you did not create this key, revoke it now, change your password and contact support.": "このキーを作成していない場合は、今すぐ無効化し、パスワードを変更してサポートにお問い合わせください。", + "If you still need API access, create a new key and update your integration.": "引き続きAPIアクセスが必要な場合は、新しいキーを作成して連携を更新してください。", + "Manage API keys": "APIキーを管理", + "Never": "無期限", + "One of your API keys was revoked": "APIキーの1つが無効化されました", + "Read and write": "読み取りと書き込み", + "Read-only": "読み取り専用", + "Review API keys": "APIキーを確認", + "This API key is invalid, has expired or has been revoked.": "このAPIキーは無効か、期限切れか、無効化されています。", + "This API key is read-only. Create a key with read and write access to make changes.": "このAPIキーは読み取り専用です。変更を行うには、読み取りと書き込みのアクセス権を持つキーを作成してください。", + "To avoid an interruption, create a new key and update your integration before then.": "中断を避けるため、それまでに新しいキーを作成して連携を更新してください。", + "Use letters, numbers, spaces and basic punctuation in the name.": "名前には文字、数字、スペース、基本的な句読点を使用してください。", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "有効なAPIキーは最大 :max 件までです。使用していないキーを無効化してから、もう一度お試しください。", + "You have view-only access, so you can only create read-only API keys.": "閲覧のみのアクセス権のため、読み取り専用のAPIキーのみ作成できます。", + "Your API key \":keyName\" expires soon": "APIキー「:keyName」の有効期限が近づいています", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": ":accountName のAPIキー「:keyName」は :date に有効期限が切れます。それ以降、このキーを使用している連携は接続できなくなります。", "Stop requiring single sign-on before removing a verified domain.": "確認済みドメインを削除する前に、シングルサインオンの必須設定を解除してください。", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google タグ マネージャーのコンテナはダッシュボードからのみ追加でき、API キーでは追加できません。", "You signed in without single sign-on": "シングルサインオンを使わずにサインインしました", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "心当たりがない場合は、すぐにパスワードを変更し、新しい認証アプリで二要素認証を設定し直してください。", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "このユーザーはシングルサインオンを必須にしているアカウントの所有者です。二要素認証はバックアップのサインイン手段なので、先にシングルサインオンの必須設定を解除するよう依頼してください。" diff --git a/backend/lang/ko.json b/backend/lang/ko.json index 9f14fe4d4e..f80e76c153 100644 --- a/backend/lang/ko.json +++ b/backend/lang/ko.json @@ -1335,7 +1335,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "ID 공급자에 연결할 수 없습니다. 연결 정보를 확인한 후 다시 시도하세요.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "싱글 사인온이 필수인 계정을 소유하고 있습니다. 2단계 인증은 예비 로그인을 보호하므로 끌 수 없습니다.", "Your single sign-on settings were changed": "싱글 사인온 설정이 변경되었습니다", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": ":appName 계정에 새 API 키가 생성되었습니다. 이 키를 가진 사람은 누구나 :accountName에 대한 회원님의 접근 권한으로 API를 사용할 수 있습니다.", + "A new API key was created on your account": "계정에 새 API 키가 생성되었습니다", + "API key not found": "API 키를 찾을 수 없습니다", + "API keys can't be used for this request. Sign in to do this instead.": "이 요청에는 API 키를 사용할 수 없습니다. 대신 로그인하여 진행하세요.", + "API keys cannot be created while impersonating a user.": "다른 사용자로 가장하는 동안에는 API 키를 만들 수 없습니다.", + "Access": "접근 권한", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": ":accountName의 관리자가 회원님의 API 키 \":keyName\"을(를) 폐기했습니다. 이 키를 사용하는 통합은 더 이상 연결할 수 없습니다.", + "Choose one of the available expiry options.": "사용 가능한 만료 옵션 중 하나를 선택하세요.", + "Confirm your email address before creating an API key.": "API 키를 만들기 전에 이메일 주소를 인증하세요.", + "Create a new key": "새 키 만들기", + "Expires": "만료", + "If you did not create this key, revoke it now, change your password and contact support.": "이 키를 만들지 않았다면 지금 바로 폐기하고 비밀번호를 변경한 뒤 고객 지원팀에 문의하세요.", + "If you still need API access, create a new key and update your integration.": "계속 API 접근이 필요하다면 새 키를 만들고 통합을 업데이트하세요.", + "Manage API keys": "API 키 관리", + "Never": "없음", + "One of your API keys was revoked": "API 키 중 하나가 폐기되었습니다", + "Read and write": "읽기 및 쓰기", + "Read-only": "읽기 전용", + "Review API keys": "API 키 검토", + "This API key is invalid, has expired or has been revoked.": "이 API 키는 유효하지 않거나, 만료되었거나, 폐기되었습니다.", + "This API key is read-only. Create a key with read and write access to make changes.": "이 API 키는 읽기 전용입니다. 변경하려면 읽기 및 쓰기 권한이 있는 키를 만드세요.", + "To avoid an interruption, create a new key and update your integration before then.": "중단을 피하려면 그 전에 새 키를 만들고 통합을 업데이트하세요.", + "Use letters, numbers, spaces and basic punctuation in the name.": "이름에는 문자, 숫자, 공백, 기본 문장 부호를 사용하세요.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "활성 API 키는 최대 :max개까지 보유할 수 있습니다. 더 이상 사용하지 않는 키를 폐기한 뒤 다시 시도하세요.", + "You have view-only access, so you can only create read-only API keys.": "보기 전용 권한이 있으므로 읽기 전용 API 키만 만들 수 있습니다.", + "Your API key \":keyName\" expires soon": "API 키 \":keyName\"이(가) 곧 만료됩니다", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": ":accountName의 API 키 \":keyName\"이(가) :date에 만료됩니다. 그 이후에는 이 키를 사용하는 통합이 더 이상 연결할 수 없습니다.", "Stop requiring single sign-on before removing a verified domain.": "확인된 도메인을 삭제하기 전에 싱글 사인온 필수 설정을 해제하세요.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google 태그 관리자 컨테이너는 API 키가 아닌 대시보드에서만 추가할 수 있습니다.", "You signed in without single sign-on": "싱글 사인온 없이 로그인했습니다", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "본인이 아니라면 즉시 비밀번호를 변경하고 새 인증 앱으로 2단계 인증을 다시 설정하세요.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "이 사용자는 싱글 사인온을 필수로 하는 계정의 소유자입니다. 2단계 인증은 백업 로그인 수단이므로 먼저 싱글 사인온 필수 설정을 해제하도록 요청하세요." diff --git a/backend/lang/nl.json b/backend/lang/nl.json index 7b1af005f4..f05571bd1d 100644 --- a/backend/lang/nl.json +++ b/backend/lang/nl.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "We konden je identity provider niet bereiken. Controleer de verbindingsgegevens en probeer het opnieuw.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Je bent eigenaar van een account waarvoor single sign-on verplicht is. Tweestapsverificatie beschermt je reserve-inlogmethode en kan daarom niet worden uitgeschakeld.", "Your single sign-on settings were changed": "Je single sign-on-instellingen zijn gewijzigd", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Er is een nieuwe API-sleutel gemaakt voor je :appName-account. Iedereen met deze sleutel kan de API gebruiken met jouw toegang tot :accountName.", + "A new API key was created on your account": "Er is een nieuwe API-sleutel gemaakt voor je account", + "API key not found": "API-sleutel niet gevonden", + "API keys can't be used for this request. Sign in to do this instead.": "API-sleutels kunnen niet worden gebruikt voor dit verzoek. Log in om dit te doen.", + "API keys cannot be created while impersonating a user.": "API-sleutels kunnen niet worden gemaakt terwijl je als een andere gebruiker bent ingelogd.", + "Access": "Toegang", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Een beheerder van :accountName heeft je API-sleutel “:keyName” ingetrokken. Integraties die deze sleutel gebruiken, kunnen geen verbinding meer maken.", + "Choose one of the available expiry options.": "Kies een van de beschikbare vervalopties.", + "Confirm your email address before creating an API key.": "Bevestig je e-mailadres voordat je een API-sleutel maakt.", + "Create a new key": "Nieuwe sleutel maken", + "Expires": "Verloopt", + "If you did not create this key, revoke it now, change your password and contact support.": "Als je deze sleutel niet hebt gemaakt, trek hem dan nu in, wijzig je wachtwoord en neem contact op met support.", + "If you still need API access, create a new key and update your integration.": "Als je nog steeds API-toegang nodig hebt, maak dan een nieuwe sleutel en werk je integratie bij.", + "Manage API keys": "API-sleutels beheren", + "Never": "Nooit", + "One of your API keys was revoked": "Een van je API-sleutels is ingetrokken", + "Read and write": "Lezen en schrijven", + "Read-only": "Alleen lezen", + "Review API keys": "API-sleutels bekijken", + "This API key is invalid, has expired or has been revoked.": "Deze API-sleutel is ongeldig, verlopen of ingetrokken.", + "This API key is read-only. Create a key with read and write access to make changes.": "Deze API-sleutel is alleen-lezen. Maak een sleutel met lees- en schrijftoegang om wijzigingen aan te brengen.", + "To avoid an interruption, create a new key and update your integration before then.": "Maak vóór die datum een nieuwe sleutel en werk je integratie bij om onderbreking te voorkomen.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Gebruik letters, cijfers, spaties en eenvoudige leestekens in de naam.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Je kunt maximaal :max actieve API-sleutels hebben. Trek er een in die je niet meer gebruikt en probeer het opnieuw.", + "You have view-only access, so you can only create read-only API keys.": "Je hebt alleen leestoegang en kunt daarom alleen API-sleutels met leestoegang maken.", + "Your API key \":keyName\" expires soon": "Je API-sleutel “:keyName” verloopt binnenkort", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Je API-sleutel “:keyName” voor :accountName verloopt op :date. Daarna kunnen integraties die deze sleutel gebruiken geen verbinding meer maken.", "Stop requiring single sign-on before removing a verified domain.": "Stop met het verplichten van single sign-on voordat je een geverifieerd domein verwijdert.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google Tag Manager-containers kun je alleen via het dashboard toevoegen, niet met een API-sleutel.", "You signed in without single sign-on": "Je bent ingelogd zonder single sign-on", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Was jij dit niet? Wijzig dan meteen je wachtwoord en stel tweestapsverificatie opnieuw in met een nieuwe authenticator-app.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Deze gebruiker is eigenaar van een account dat single sign-on verplicht. Tweestapsverificatie is hun noodtoegang, dus vraag hen eerst om single sign-on niet langer te verplichten." diff --git a/backend/lang/pl.json b/backend/lang/pl.json index b456388e12..ae2af722f6 100644 --- a/backend/lang/pl.json +++ b/backend/lang/pl.json @@ -1334,7 +1334,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Nie udało się połączyć z Twoim dostawcą tożsamości. Sprawdź dane połączenia i spróbuj ponownie.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Jesteś właścicielem konta, które wymaga logowania jednokrotnego. Uwierzytelnianie dwuskładnikowe chroni Twoje awaryjne logowanie, więc nie można go wyłączyć.", "Your single sign-on settings were changed": "Ustawienia logowania jednokrotnego zostały zmienione", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Na Twoim koncie :appName utworzono nowy klucz API. Każdy, kto ma ten klucz, może korzystać z API z Twoim dostępem do :accountName.", + "A new API key was created on your account": "Na Twoim koncie utworzono nowy klucz API", + "API key not found": "Nie znaleziono klucza API", + "API keys can't be used for this request. Sign in to do this instead.": "Kluczy API nie można użyć do tego żądania. Zamiast tego zaloguj się.", + "API keys cannot be created while impersonating a user.": "Nie można tworzyć kluczy API podczas działania jako inny użytkownik.", + "Access": "Dostęp", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Administrator :accountName unieważnił Twój klucz API „:keyName”. Integracje, które go używają, nie mogą się już połączyć.", + "Choose one of the available expiry options.": "Wybierz jedną z dostępnych opcji ważności.", + "Confirm your email address before creating an API key.": "Potwierdź swój adres e-mail przed utworzeniem klucza API.", + "Create a new key": "Utwórz nowy klucz", + "Expires": "Wygasa", + "If you did not create this key, revoke it now, change your password and contact support.": "Jeśli ten klucz nie został utworzony przez Ciebie, natychmiast go unieważnij, zmień hasło i skontaktuj się z pomocą techniczną.", + "If you still need API access, create a new key and update your integration.": "Jeśli nadal potrzebujesz dostępu do API, utwórz nowy klucz i zaktualizuj integrację.", + "Manage API keys": "Zarządzaj kluczami API", + "Never": "Nigdy", + "One of your API keys was revoked": "Jeden z Twoich kluczy API został unieważniony", + "Read and write": "Odczyt i zapis", + "Read-only": "Tylko odczyt", + "Review API keys": "Przejrzyj klucze API", + "This API key is invalid, has expired or has been revoked.": "Ten klucz API jest nieprawidłowy, wygasł lub został unieważniony.", + "This API key is read-only. Create a key with read and write access to make changes.": "Ten klucz API umożliwia tylko odczyt. Aby wprowadzać zmiany, utwórz klucz z dostępem do odczytu i zapisu.", + "To avoid an interruption, create a new key and update your integration before then.": "Aby uniknąć przerwy w działaniu, utwórz wcześniej nowy klucz i zaktualizuj integrację.", + "Use letters, numbers, spaces and basic punctuation in the name.": "W nazwie używaj liter, cyfr, spacji i podstawowych znaków interpunkcyjnych.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Możesz mieć maksymalnie :max aktywnych kluczy API. Unieważnij klucz, którego już nie używasz, i spróbuj ponownie.", + "You have view-only access, so you can only create read-only API keys.": "Masz dostęp tylko do podglądu, więc możesz tworzyć wyłącznie klucze API tylko do odczytu.", + "Your API key \":keyName\" expires soon": "Twój klucz API „:keyName” wkrótce wygaśnie", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Twój klucz API „:keyName” dla :accountName wygasa :date. Po tym czasie integracje, które go używają, nie będą mogły się połączyć.", "Stop requiring single sign-on before removing a verified domain.": "Wyłącz wymóg logowania jednokrotnego przed usunięciem zweryfikowanej domeny.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Kontenery Google Tag Manager można dodać tylko w panelu, a nie za pomocą klucza API.", "You signed in without single sign-on": "Zalogowano się bez logowania jednokrotnego", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Jeśli to nie Ty, natychmiast zmień hasło i ponownie skonfiguruj uwierzytelnianie dwuskładnikowe z nową aplikacją uwierzytelniającą.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Ten użytkownik jest właścicielem konta, które wymaga logowania jednokrotnego. Uwierzytelnianie dwuskładnikowe to jego zapasowy sposób logowania, więc najpierw poproś go o wyłączenie wymogu logowania jednokrotnego." diff --git a/backend/lang/pt-br.json b/backend/lang/pt-br.json index ba6475e762..9c7391b42a 100644 --- a/backend/lang/pt-br.json +++ b/backend/lang/pt-br.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Não foi possível acessar o seu provedor de identidade. Verifique os dados de conexão e tente novamente.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Você é proprietário de uma conta que exige login único. A autenticação em duas etapas protege o seu login de backup, por isso não pode ser desativada.", "Your single sign-on settings were changed": "As configurações de login único foram alteradas", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Uma nova chave de API foi criada na sua conta :appName. Qualquer pessoa com esta chave pode usar a API com o seu acesso a :accountName.", + "A new API key was created on your account": "Uma nova chave de API foi criada na sua conta", + "API key not found": "Chave de API não encontrada", + "API keys can't be used for this request. Sign in to do this instead.": "Chaves de API não podem ser usadas nesta requisição. Faça login para fazer isso.", + "API keys cannot be created while impersonating a user.": "Não é possível criar chaves de API enquanto você está acessando como outro usuário.", + "Access": "Acesso", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Um administrador de :accountName revogou sua chave de API \":keyName\". As integrações que a usam não conseguem mais se conectar.", + "Choose one of the available expiry options.": "Escolha uma das opções de expiração disponíveis.", + "Confirm your email address before creating an API key.": "Confirme seu endereço de e-mail antes de criar uma chave de API.", + "Create a new key": "Criar uma nova chave", + "Expires": "Expira em", + "If you did not create this key, revoke it now, change your password and contact support.": "Se você não criou esta chave, revogue-a agora, altere sua senha e entre em contato com o suporte.", + "If you still need API access, create a new key and update your integration.": "Se você ainda precisa de acesso à API, crie uma nova chave e atualize sua integração.", + "Manage API keys": "Gerenciar chaves de API", + "Never": "Nunca", + "One of your API keys was revoked": "Uma das suas chaves de API foi revogada", + "Read and write": "Leitura e escrita", + "Read-only": "Somente leitura", + "Review API keys": "Revisar chaves de API", + "This API key is invalid, has expired or has been revoked.": "Esta chave de API é inválida, expirou ou foi revogada.", + "This API key is read-only. Create a key with read and write access to make changes.": "Esta chave de API é somente leitura. Crie uma chave com acesso de leitura e escrita para fazer alterações.", + "To avoid an interruption, create a new key and update your integration before then.": "Para evitar interrupções, crie uma nova chave e atualize sua integração antes disso.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Use letras, números, espaços e pontuação básica no nome.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Você pode ter até :max chaves de API ativas. Revogue uma que não usa mais e tente novamente.", + "You have view-only access, so you can only create read-only API keys.": "Você tem acesso somente de visualização, então só pode criar chaves de API somente leitura.", + "Your API key \":keyName\" expires soon": "Sua chave de API \":keyName\" expira em breve", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Sua chave de API \":keyName\" para :accountName expira em :date. Depois disso, as integrações que a usam não conseguirão mais se conectar.", "Stop requiring single sign-on before removing a verified domain.": "Deixe de exigir o login único antes de remover um domínio verificado.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Contêineres do Google Tag Manager só podem ser adicionados pelo painel, não com uma chave de API.", "You signed in without single sign-on": "Você entrou sem o login único", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Se não foi você, altere sua senha e configure a autenticação de dois fatores novamente com um novo aplicativo autenticador imediatamente.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Este usuário é proprietário de uma conta que exige login único. A autenticação de dois fatores é o acesso de emergência dele, então peça primeiro que deixe de exigir o login único." diff --git a/backend/lang/pt.json b/backend/lang/pt.json index ecb42450cd..a3ef2f2406 100644 --- a/backend/lang/pt.json +++ b/backend/lang/pt.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Não foi possível contactar o seu fornecedor de identidade. Verifique os dados de ligação e tente novamente.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "É proprietário de uma conta que exige início de sessão único. A autenticação de dois fatores protege o seu início de sessão de recurso, por isso não pode ser desativada.", "Your single sign-on settings were changed": "As definições de início de sessão único foram alteradas", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Foi criada uma nova chave de API na sua conta :appName. Qualquer pessoa com esta chave pode usar a API com o seu acesso a :accountName.", + "A new API key was created on your account": "Foi criada uma nova chave de API na sua conta", + "API key not found": "Chave de API não encontrada", + "API keys can't be used for this request. Sign in to do this instead.": "As chaves de API não podem ser usadas neste pedido. Em vez disso, inicie sessão para o fazer.", + "API keys cannot be created while impersonating a user.": "Não é possível criar chaves de API enquanto age em nome de outro utilizador.", + "Access": "Acesso", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Um administrador de :accountName revogou a sua chave de API \":keyName\". As integrações que a utilizam já não conseguem ligar-se.", + "Choose one of the available expiry options.": "Escolha uma das opções de validade disponíveis.", + "Confirm your email address before creating an API key.": "Confirme o seu endereço de e-mail antes de criar uma chave de API.", + "Create a new key": "Criar uma nova chave", + "Expires": "Expira", + "If you did not create this key, revoke it now, change your password and contact support.": "Se não criou esta chave, revogue-a já, altere a sua palavra-passe e contacte o suporte.", + "If you still need API access, create a new key and update your integration.": "Se ainda precisa de acesso à API, crie uma nova chave e atualize a sua integração.", + "Manage API keys": "Gerir chaves de API", + "Never": "Nunca", + "One of your API keys was revoked": "Uma das suas chaves de API foi revogada", + "Read and write": "Leitura e escrita", + "Read-only": "Apenas leitura", + "Review API keys": "Rever chaves de API", + "This API key is invalid, has expired or has been revoked.": "Esta chave de API é inválida, expirou ou foi revogada.", + "This API key is read-only. Create a key with read and write access to make changes.": "Esta chave de API é apenas de leitura. Crie uma chave com acesso de leitura e escrita para fazer alterações.", + "To avoid an interruption, create a new key and update your integration before then.": "Para evitar interrupções, crie uma nova chave e atualize a sua integração antes dessa data.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Utilize letras, números, espaços e pontuação básica no nome.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Pode ter até :max chaves de API ativas. Revogue uma que já não utilize e tente novamente.", + "You have view-only access, so you can only create read-only API keys.": "Tem acesso apenas de visualização, por isso só pode criar chaves de API apenas de leitura.", + "Your API key \":keyName\" expires soon": "A sua chave de API \":keyName\" expira em breve", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "A sua chave de API \":keyName\" para :accountName expira a :date. Depois disso, as integrações que a utilizam deixarão de conseguir ligar-se.", "Stop requiring single sign-on before removing a verified domain.": "Deixe de exigir o início de sessão único antes de remover um domínio verificado.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Os contentores do Google Tag Manager só podem ser adicionados no painel, não com uma chave de API.", "You signed in without single sign-on": "Iniciou sessão sem o início de sessão único", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Se não foi você, altere imediatamente a sua palavra-passe e configure novamente a autenticação de dois fatores com uma nova aplicação de autenticação.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Este utilizador é proprietário de uma conta que exige o início de sessão único. A autenticação de dois fatores é o seu acesso de emergência, por isso peça-lhe primeiro que deixe de exigir o início de sessão único." diff --git a/backend/lang/ru.json b/backend/lang/ru.json index c7adfe4c0f..169f2f6b43 100644 --- a/backend/lang/ru.json +++ b/backend/lang/ru.json @@ -1086,7 +1086,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Не удалось связаться с вашим поставщиком удостоверений. Проверьте данные подключения и повторите попытку.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Вы владеете аккаунтом, для которого требуется единый вход. Двухфакторная аутентификация защищает ваш резервный вход, поэтому её нельзя отключить.", "Your single sign-on settings were changed": "Настройки единого входа изменены", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "В вашем аккаунте :appName создан новый API-ключ. Любой, у кого есть этот ключ, может использовать API с вашим доступом к :accountName.", + "A new API key was created on your account": "В вашем аккаунте создан новый API-ключ", + "API key not found": "API-ключ не найден", + "API keys can't be used for this request. Sign in to do this instead.": "Для этого запроса нельзя использовать API-ключи. Войдите в систему, чтобы выполнить это действие.", + "API keys cannot be created while impersonating a user.": "Нельзя создавать API-ключи, действуя от имени другого пользователя.", + "Access": "Доступ", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Администратор :accountName отозвал ваш API-ключ «:keyName». Интеграции, которые его используют, больше не смогут подключиться.", + "Choose one of the available expiry options.": "Выберите один из доступных сроков действия.", + "Confirm your email address before creating an API key.": "Подтвердите адрес электронной почты, прежде чем создавать API-ключ.", + "Create a new key": "Создать новый ключ", + "Expires": "Истекает", + "If you did not create this key, revoke it now, change your password and contact support.": "Если вы не создавали этот ключ, сразу отзовите его, смените пароль и обратитесь в поддержку.", + "If you still need API access, create a new key and update your integration.": "Если вам всё ещё нужен доступ к API, создайте новый ключ и обновите интеграцию.", + "Manage API keys": "Управление API-ключами", + "Never": "Никогда", + "One of your API keys was revoked": "Один из ваших API-ключей отозван", + "Read and write": "Чтение и запись", + "Read-only": "Только чтение", + "Review API keys": "Проверить API-ключи", + "This API key is invalid, has expired or has been revoked.": "Этот API-ключ недействителен, истёк или был отозван.", + "This API key is read-only. Create a key with read and write access to make changes.": "Этот API-ключ только для чтения. Чтобы вносить изменения, создайте ключ с доступом на чтение и запись.", + "To avoid an interruption, create a new key and update your integration before then.": "Чтобы избежать перебоев, заранее создайте новый ключ и обновите интеграцию.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Используйте в названии буквы, цифры, пробелы и основные знаки препинания.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "У вас может быть не более :max активных API-ключей. Отзовите неиспользуемый ключ и повторите попытку.", + "You have view-only access, so you can only create read-only API keys.": "У вас доступ только для просмотра, поэтому вы можете создавать только API-ключи с доступом на чтение.", + "Your API key \":keyName\" expires soon": "Срок действия вашего API-ключа «:keyName» скоро истекает", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Срок действия вашего API-ключа «:keyName» для :accountName истекает :date. После этого интеграции, которые его используют, не смогут подключиться.", "Stop requiring single sign-on before removing a verified domain.": "Отключите обязательный единый вход, прежде чем удалять подтверждённый домен.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Контейнеры Google Tag Manager можно добавить только в панели управления, а не с помощью API-ключа.", "You signed in without single sign-on": "Вы вошли без единого входа", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Если это были не вы, немедленно смените пароль и заново настройте двухфакторную аутентификацию в новом приложении-аутентификаторе.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Этот пользователь владеет аккаунтом, где обязателен единый вход. Двухфакторная аутентификация — его резервный способ входа, поэтому сначала попросите его отключить обязательный единый вход." diff --git a/backend/lang/se.json b/backend/lang/se.json index 55292c44bb..cba3bbedc1 100644 --- a/backend/lang/se.json +++ b/backend/lang/se.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Vi kunde inte nå din identitetsleverantör. Kontrollera anslutningsuppgifterna och försök igen.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Du äger ett konto som kräver enkel inloggning. Tvåfaktorsautentisering skyddar din reservinloggning, så den kan inte stängas av.", "Your single sign-on settings were changed": "Dina inställningar för enkel inloggning ändrades", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "En ny API-nyckel har skapats på ditt :appName-konto. Alla som har nyckeln kan använda API-gränssnittet med din åtkomst till :accountName.", + "A new API key was created on your account": "En ny API-nyckel har skapats på ditt konto", + "API key not found": "API-nyckeln hittades inte", + "API keys can't be used for this request. Sign in to do this instead.": "API-nycklar kan inte användas för den här förfrågan. Logga in för att göra detta i stället.", + "API keys cannot be created while impersonating a user.": "API-nycklar kan inte skapas medan du agerar som en annan användare.", + "Access": "Åtkomst", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "En administratör för :accountName har återkallat din API-nyckel ”:keyName”. Integrationer som använder den kan inte längre ansluta.", + "Choose one of the available expiry options.": "Välj ett av de tillgängliga alternativen för giltighetstid.", + "Confirm your email address before creating an API key.": "Bekräfta din e-postadress innan du skapar en API-nyckel.", + "Create a new key": "Skapa en ny nyckel", + "Expires": "Går ut", + "If you did not create this key, revoke it now, change your password and contact support.": "Om du inte skapade den här nyckeln, återkalla den nu, byt lösenord och kontakta supporten.", + "If you still need API access, create a new key and update your integration.": "Om du fortfarande behöver API-åtkomst, skapa en ny nyckel och uppdatera din integration.", + "Manage API keys": "Hantera API-nycklar", + "Never": "Aldrig", + "One of your API keys was revoked": "En av dina API-nycklar har återkallats", + "Read and write": "Läsa och skriva", + "Read-only": "Skrivskyddad", + "Review API keys": "Granska API-nycklar", + "This API key is invalid, has expired or has been revoked.": "Den här API-nyckeln är ogiltig, har gått ut eller har återkallats.", + "This API key is read-only. Create a key with read and write access to make changes.": "Den här API-nyckeln är skrivskyddad. Skapa en nyckel med läs- och skrivåtkomst för att göra ändringar.", + "To avoid an interruption, create a new key and update your integration before then.": "Undvik avbrott genom att skapa en ny nyckel och uppdatera din integration innan dess.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Använd bokstäver, siffror, mellanslag och vanliga skiljetecken i namnet.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Du kan ha högst :max aktiva API-nycklar. Återkalla en som du inte längre använder och försök igen.", + "You have view-only access, so you can only create read-only API keys.": "Du har endast visningsåtkomst och kan därför bara skapa skrivskyddade API-nycklar.", + "Your API key \":keyName\" expires soon": "Din API-nyckel ”:keyName” går snart ut", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Din API-nyckel ”:keyName” för :accountName går ut :date. Efter det kan integrationer som använder den inte längre ansluta.", "Stop requiring single sign-on before removing a verified domain.": "Sluta kräva enkel inloggning innan du tar bort en verifierad domän.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google Tag Manager-behållare kan bara läggas till från instrumentpanelen, inte med en API-nyckel.", "You signed in without single sign-on": "Du loggade in utan enkel inloggning", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Om det inte var du, byt lösenord direkt och ställ in tvåfaktorsautentisering igen med en ny autentiseringsapp.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Den här användaren äger ett konto som kräver enkel inloggning. Tvåfaktorsautentisering är deras reservinloggning, så be dem först sluta kräva enkel inloggning." diff --git a/backend/lang/sk.json b/backend/lang/sk.json index 18b546cfe6..31c8c004ee 100644 --- a/backend/lang/sk.json +++ b/backend/lang/sk.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Nepodarilo sa nám spojiť s vaším poskytovateľom identity. Skontrolujte údaje o pripojení a skúste to znova.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Vlastníte účet, ktorý vyžaduje jednotné prihlásenie. Dvojfaktorové overenie chráni vaše záložné prihlásenie, preto ho nemožno vypnúť.", "Your single sign-on settings were changed": "Nastavenia jednotného prihlásenia boli zmenené", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Vo vašom účte :appName bol vytvorený nový API kľúč. Ktokoľvek s týmto kľúčom môže používať API s vaším prístupom k :accountName.", + "A new API key was created on your account": "Vo vašom účte bol vytvorený nový API kľúč", + "API key not found": "API kľúč sa nenašiel", + "API keys can't be used for this request. Sign in to do this instead.": "API kľúče nemožno použiť na túto požiadavku. Namiesto toho sa prihláste.", + "API keys cannot be created while impersonating a user.": "API kľúče nemožno vytvárať počas vystupovania za iného používateľa.", + "Access": "Prístup", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Administrátor účtu :accountName odvolal váš API kľúč „:keyName“. Integrácie, ktoré ho používajú, sa už nemôžu pripojiť.", + "Choose one of the available expiry options.": "Vyberte jednu z dostupných možností platnosti.", + "Confirm your email address before creating an API key.": "Pred vytvorením API kľúča potvrďte svoju e-mailovú adresu.", + "Create a new key": "Vytvoriť nový kľúč", + "Expires": "Platnosť do", + "If you did not create this key, revoke it now, change your password and contact support.": "Ak ste tento kľúč nevytvorili vy, ihneď ho odvolajte, zmeňte si heslo a kontaktujte podporu.", + "If you still need API access, create a new key and update your integration.": "Ak stále potrebujete prístup k API, vytvorte nový kľúč a aktualizujte svoju integráciu.", + "Manage API keys": "Spravovať API kľúče", + "Never": "Nikdy", + "One of your API keys was revoked": "Jeden z vašich API kľúčov bol odvolaný", + "Read and write": "Čítanie a zápis", + "Read-only": "Iba na čítanie", + "Review API keys": "Skontrolovať API kľúče", + "This API key is invalid, has expired or has been revoked.": "Tento API kľúč je neplatný, jeho platnosť vypršala alebo bol odvolaný.", + "This API key is read-only. Create a key with read and write access to make changes.": "Tento API kľúč je iba na čítanie. Ak chcete robiť zmeny, vytvorte kľúč s prístupom na čítanie a zápis.", + "To avoid an interruption, create a new key and update your integration before then.": "Aby ste predišli prerušeniu, vytvorte ešte predtým nový kľúč a aktualizujte svoju integráciu.", + "Use letters, numbers, spaces and basic punctuation in the name.": "V názve použite písmená, číslice, medzery a základnú interpunkciu.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Môžete mať najviac :max aktívnych API kľúčov. Odvolajte kľúč, ktorý už nepoužívate, a skúste to znova.", + "You have view-only access, so you can only create read-only API keys.": "Máte prístup iba na zobrazenie, preto môžete vytvárať iba API kľúče na čítanie.", + "Your API key \":keyName\" expires soon": "Platnosť vášho API kľúča „:keyName“ čoskoro vyprší", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Platnosť vášho API kľúča „:keyName“ pre :accountName vyprší :date. Potom sa integrácie, ktoré ho používajú, už nebudú môcť pripojiť.", "Stop requiring single sign-on before removing a verified domain.": "Pred odstránením overenej domény vypnite povinné jednotné prihlásenie.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Kontajnery Google Tag Manager je možné pridať iba z ovládacieho panela, nie pomocou kľúča API.", "You signed in without single sign-on": "Prihlásili ste sa bez jednotného prihlásenia", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Ak ste to neboli vy, okamžite si zmeňte heslo a znova nastavte dvojfaktorové overenie s novou overovacou aplikáciou.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Tento používateľ vlastní účet, ktorý vyžaduje jednotné prihlásenie. Dvojfaktorové overenie je jeho záložné prihlásenie, preto ho najprv požiadajte, aby vypol povinné jednotné prihlásenie." diff --git a/backend/lang/tr.json b/backend/lang/tr.json index 8a59bfddbc..1dfa9cf2f4 100644 --- a/backend/lang/tr.json +++ b/backend/lang/tr.json @@ -1161,7 +1161,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Kimlik sağlayıcınıza ulaşamadık. Bağlantı bilgilerini kontrol edip tekrar deneyin.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Tek oturum açma gerektiren bir hesabın sahibisiniz. İki adımlı doğrulama yedek girişinizi koruduğu için kapatılamaz.", "Your single sign-on settings were changed": "Tek oturum açma ayarlarınız değiştirildi", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": ":appName hesabınızda yeni bir API anahtarı oluşturuldu. Bu anahtara sahip olan herkes, :accountName hesabına olan erişiminizle API'yi kullanabilir.", + "A new API key was created on your account": "Hesabınızda yeni bir API anahtarı oluşturuldu", + "API key not found": "API anahtarı bulunamadı", + "API keys can't be used for this request. Sign in to do this instead.": "Bu istek için API anahtarları kullanılamaz. Bunun yerine oturum açarak yapın.", + "API keys cannot be created while impersonating a user.": "Başka bir kullanıcı adına işlem yaparken API anahtarı oluşturulamaz.", + "Access": "Erişim", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": ":accountName yöneticisi \":keyName\" API anahtarınızı iptal etti. Bu anahtarı kullanan entegrasyonlar artık bağlanamaz.", + "Choose one of the available expiry options.": "Mevcut geçerlilik süresi seçeneklerinden birini seçin.", + "Confirm your email address before creating an API key.": "API anahtarı oluşturmadan önce e-posta adresinizi onaylayın.", + "Create a new key": "Yeni anahtar oluştur", + "Expires": "Sona erme", + "If you did not create this key, revoke it now, change your password and contact support.": "Bu anahtarı siz oluşturmadıysanız hemen iptal edin, parolanızı değiştirin ve destek ekibiyle iletişime geçin.", + "If you still need API access, create a new key and update your integration.": "Hâlâ API erişimine ihtiyacınız varsa yeni bir anahtar oluşturun ve entegrasyonunuzu güncelleyin.", + "Manage API keys": "API anahtarlarını yönet", + "Never": "Hiçbir zaman", + "One of your API keys was revoked": "API anahtarlarınızdan biri iptal edildi", + "Read and write": "Okuma ve yazma", + "Read-only": "Salt okunur", + "Review API keys": "API anahtarlarını gözden geçir", + "This API key is invalid, has expired or has been revoked.": "Bu API anahtarı geçersiz, süresi dolmuş veya iptal edilmiş.", + "This API key is read-only. Create a key with read and write access to make changes.": "Bu API anahtarı salt okunurdur. Değişiklik yapmak için okuma ve yazma erişimine sahip bir anahtar oluşturun.", + "To avoid an interruption, create a new key and update your integration before then.": "Kesinti yaşamamak için bu tarihten önce yeni bir anahtar oluşturun ve entegrasyonunuzu güncelleyin.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Adda harf, rakam, boşluk ve temel noktalama işaretleri kullanın.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "En fazla :max etkin API anahtarınız olabilir. Artık kullanmadığınız birini iptal edip tekrar deneyin.", + "You have view-only access, so you can only create read-only API keys.": "Yalnızca görüntüleme erişiminiz olduğundan yalnızca salt okunur API anahtarları oluşturabilirsiniz.", + "Your API key \":keyName\" expires soon": "\":keyName\" API anahtarınızın süresi yakında doluyor", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": ":accountName için \":keyName\" API anahtarınızın süresi :date tarihinde doluyor. Bundan sonra bu anahtarı kullanan entegrasyonlar bağlanamaz.", "Stop requiring single sign-on before removing a verified domain.": "Doğrulanmış bir alan adını kaldırmadan önce çoklu oturum açma zorunluluğunu kapatın.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google Etiket Yöneticisi kapsayıcıları yalnızca panelden eklenebilir, API anahtarıyla eklenemez.", "You signed in without single sign-on": "Çoklu oturum açma olmadan giriş yaptınız", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Bu siz değilseniz, parolanızı hemen değiştirin ve iki faktörlü kimlik doğrulamayı yeni bir doğrulayıcı uygulamayla yeniden kurun.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Bu kullanıcı çoklu oturum açmayı zorunlu tutan bir hesabın sahibi. İki faktörlü kimlik doğrulama onun yedek giriş yöntemidir, bu yüzden önce çoklu oturum açma zorunluluğunu kapatmasını isteyin." diff --git a/backend/lang/vi.json b/backend/lang/vi.json index 9e42e388d5..4370679082 100644 --- a/backend/lang/vi.json +++ b/backend/lang/vi.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "Chúng tôi không thể kết nối với nhà cung cấp danh tính của bạn. Hãy kiểm tra thông tin kết nối và thử lại.", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "Bạn sở hữu một tài khoản yêu cầu đăng nhập một lần. Xác thực hai yếu tố bảo vệ cách đăng nhập dự phòng của bạn nên không thể tắt.", "Your single sign-on settings were changed": "Cài đặt đăng nhập một lần của bạn đã được thay đổi", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "Một khóa API mới đã được tạo trên tài khoản :appName của bạn. Bất kỳ ai có khóa này đều có thể dùng API với quyền truy cập của bạn vào :accountName.", + "A new API key was created on your account": "Một khóa API mới đã được tạo trên tài khoản của bạn", + "API key not found": "Không tìm thấy khóa API", + "API keys can't be used for this request. Sign in to do this instead.": "Không thể dùng khóa API cho yêu cầu này. Hãy đăng nhập để thực hiện.", + "API keys cannot be created while impersonating a user.": "Không thể tạo khóa API khi đang đăng nhập với tư cách người dùng khác.", + "Access": "Quyền truy cập", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": "Quản trị viên của :accountName đã thu hồi khóa API \":keyName\" của bạn. Các tích hợp dùng khóa này không thể kết nối nữa.", + "Choose one of the available expiry options.": "Hãy chọn một trong các tùy chọn thời hạn có sẵn.", + "Confirm your email address before creating an API key.": "Hãy xác nhận địa chỉ email của bạn trước khi tạo khóa API.", + "Create a new key": "Tạo khóa mới", + "Expires": "Hết hạn", + "If you did not create this key, revoke it now, change your password and contact support.": "Nếu bạn không tạo khóa này, hãy thu hồi ngay, đổi mật khẩu và liên hệ bộ phận hỗ trợ.", + "If you still need API access, create a new key and update your integration.": "Nếu bạn vẫn cần truy cập API, hãy tạo khóa mới và cập nhật tích hợp của bạn.", + "Manage API keys": "Quản lý khóa API", + "Never": "Không bao giờ", + "One of your API keys was revoked": "Một khóa API của bạn đã bị thu hồi", + "Read and write": "Đọc và ghi", + "Read-only": "Chỉ đọc", + "Review API keys": "Xem lại khóa API", + "This API key is invalid, has expired or has been revoked.": "Khóa API này không hợp lệ, đã hết hạn hoặc đã bị thu hồi.", + "This API key is read-only. Create a key with read and write access to make changes.": "Khóa API này chỉ có quyền đọc. Hãy tạo khóa có quyền đọc và ghi để thực hiện thay đổi.", + "To avoid an interruption, create a new key and update your integration before then.": "Để tránh gián đoạn, hãy tạo khóa mới và cập nhật tích hợp của bạn trước thời điểm đó.", + "Use letters, numbers, spaces and basic punctuation in the name.": "Hãy dùng chữ cái, chữ số, dấu cách và dấu câu cơ bản trong tên.", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "Bạn có thể có tối đa :max khóa API đang hoạt động. Hãy thu hồi một khóa không còn dùng và thử lại.", + "You have view-only access, so you can only create read-only API keys.": "Bạn chỉ có quyền xem nên chỉ có thể tạo khóa API chỉ đọc.", + "Your API key \":keyName\" expires soon": "Khóa API \":keyName\" của bạn sắp hết hạn", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "Khóa API \":keyName\" của bạn cho :accountName sẽ hết hạn vào :date. Sau thời điểm đó, các tích hợp dùng khóa này sẽ không thể kết nối nữa.", "Stop requiring single sign-on before removing a verified domain.": "Hãy tắt yêu cầu đăng nhập một lần trước khi xóa một tên miền đã xác minh.", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Chỉ có thể thêm vùng chứa Google Tag Manager từ bảng điều khiển, không thể thêm bằng khóa API.", "You signed in without single sign-on": "Bạn đã đăng nhập mà không dùng đăng nhập một lần", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "Nếu đó không phải là bạn, hãy đổi mật khẩu ngay và thiết lập lại xác thực hai yếu tố bằng một ứng dụng xác thực mới.", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "Người dùng này sở hữu một tài khoản bắt buộc đăng nhập một lần. Xác thực hai yếu tố là cách đăng nhập dự phòng của họ, vì vậy hãy yêu cầu họ tắt yêu cầu đăng nhập một lần trước." diff --git a/backend/lang/zh-cn.json b/backend/lang/zh-cn.json index 73886b3138..6c3b7f4e0b 100644 --- a/backend/lang/zh-cn.json +++ b/backend/lang/zh-cn.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "无法连接到您的身份提供商。请检查连接信息后重试。", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "您拥有的账户要求使用单点登录。双重验证用于保护您的备用登录,因此无法关闭。", "Your single sign-on settings were changed": "您的单点登录设置已更改", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "您的 :appName 账户中创建了一个新的 API 密钥。任何拥有此密钥的人都可以使用您对 :accountName 的访问权限调用 API。", + "A new API key was created on your account": "您的账户中创建了一个新的 API 密钥", + "API key not found": "未找到 API 密钥", + "API keys can't be used for this request. Sign in to do this instead.": "此请求不能使用 API 密钥。请登录后再执行此操作。", + "API keys cannot be created while impersonating a user.": "以其他用户身份操作期间无法创建 API 密钥。", + "Access": "访问权限", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": ":accountName 的管理员已撤销您的 API 密钥“:keyName”。使用该密钥的集成将无法再连接。", + "Choose one of the available expiry options.": "请选择一个可用的有效期选项。", + "Confirm your email address before creating an API key.": "请先确认您的电子邮件地址,再创建 API 密钥。", + "Create a new key": "创建新密钥", + "Expires": "过期时间", + "If you did not create this key, revoke it now, change your password and contact support.": "如果此密钥不是您创建的,请立即撤销,修改密码并联系客服。", + "If you still need API access, create a new key and update your integration.": "如果您仍需要 API 访问权限,请创建新密钥并更新您的集成。", + "Manage API keys": "管理 API 密钥", + "Never": "永不", + "One of your API keys was revoked": "您的一个 API 密钥已被撤销", + "Read and write": "读写", + "Read-only": "只读", + "Review API keys": "查看 API 密钥", + "This API key is invalid, has expired or has been revoked.": "此 API 密钥无效、已过期或已被撤销。", + "This API key is read-only. Create a key with read and write access to make changes.": "此 API 密钥为只读。如需进行更改,请创建具有读写权限的密钥。", + "To avoid an interruption, create a new key and update your integration before then.": "为避免中断,请在此之前创建新密钥并更新您的集成。", + "Use letters, numbers, spaces and basic punctuation in the name.": "名称中请使用字母、数字、空格和基本标点符号。", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "您最多可以拥有 :max 个有效 API 密钥。请撤销一个不再使用的密钥后重试。", + "You have view-only access, so you can only create read-only API keys.": "您只有查看权限,因此只能创建只读 API 密钥。", + "Your API key \":keyName\" expires soon": "您的 API 密钥“:keyName”即将过期", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "您用于 :accountName 的 API 密钥“:keyName”将于 :date 过期。之后,使用该密钥的集成将无法再连接。", "Stop requiring single sign-on before removing a verified domain.": "请先停止强制使用单点登录,再移除已验证的域名。", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google 跟踪代码管理器容器只能在控制台中添加,不能通过 API 密钥添加。", "You signed in without single sign-on": "您在未使用单点登录的情况下登录了", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "如果不是您本人操作,请立即更改密码,并使用新的身份验证器应用重新设置双重身份验证。", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "该用户拥有一个强制使用单点登录的账户。双重身份验证是其备用登录方式,请先让其停止强制使用单点登录。" diff --git a/backend/lang/zh-hk.json b/backend/lang/zh-hk.json index 447152886b..1d93cd6012 100644 --- a/backend/lang/zh-hk.json +++ b/backend/lang/zh-hk.json @@ -1146,7 +1146,35 @@ "We couldn't reach your identity provider. Check the connection details and try again.": "無法連接到你的身份提供者。請檢查連接資料後重試。", "You own an account that requires single sign-on. Two-factor authentication protects your backup sign-in, so it cannot be turned off.": "你擁有的帳戶要求使用單一登入。雙重驗證用於保護你的備用登入,因此無法關閉。", "Your single sign-on settings were changed": "你的單一登入設定已更改", + "A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.": "您的 :appName 帳戶已建立新的 API 金鑰。任何擁有此金鑰的人都可以使用您對 :accountName 的存取權限使用 API。", + "A new API key was created on your account": "您的帳戶已建立新的 API 金鑰", + "API key not found": "找不到 API 金鑰", + "API keys can't be used for this request. Sign in to do this instead.": "此請求不能使用 API 金鑰。請登入後再執行此操作。", + "API keys cannot be created while impersonating a user.": "以其他使用者身分操作期間無法建立 API 金鑰。", + "Access": "存取權限", + "An administrator of :accountName revoked your API key \":keyName\". Integrations that use it can no longer connect.": ":accountName 的管理員已撤銷您的 API 金鑰「:keyName」。使用該金鑰的整合將無法再連接。", + "Choose one of the available expiry options.": "請選擇一個可用的有效期選項。", + "Confirm your email address before creating an API key.": "請先確認您的電郵地址,再建立 API 金鑰。", + "Create a new key": "建立新金鑰", + "Expires": "到期時間", + "If you did not create this key, revoke it now, change your password and contact support.": "如果此金鑰不是您建立的,請立即撤銷、更改密碼並聯絡客戶支援。", + "If you still need API access, create a new key and update your integration.": "如果您仍需要 API 存取權限,請建立新金鑰並更新您的整合。", + "Manage API keys": "管理 API 金鑰", + "Never": "永不", + "One of your API keys was revoked": "您的一個 API 金鑰已被撤銷", + "Read and write": "讀寫", + "Read-only": "唯讀", + "Review API keys": "查看 API 金鑰", + "This API key is invalid, has expired or has been revoked.": "此 API 金鑰無效、已過期或已被撤銷。", + "This API key is read-only. Create a key with read and write access to make changes.": "此 API 金鑰為唯讀。如需進行更改,請建立具有讀寫權限的金鑰。", + "To avoid an interruption, create a new key and update your integration before then.": "為避免中斷,請在此之前建立新金鑰並更新您的整合。", + "Use letters, numbers, spaces and basic punctuation in the name.": "名稱中請使用字母、數字、空格和基本標點符號。", + "You can have up to :max active API keys. Revoke one you no longer use and try again.": "您最多可以擁有 :max 個有效 API 金鑰。請撤銷一個不再使用的金鑰後重試。", + "You have view-only access, so you can only create read-only API keys.": "您只有查看權限,因此只能建立唯讀 API 金鑰。", + "Your API key \":keyName\" expires soon": "您的 API 金鑰「:keyName」即將到期", + "Your API key \":keyName\" for :accountName expires on :date. After that, integrations that use it can no longer connect.": "您用於 :accountName 的 API 金鑰「:keyName」將於 :date 到期。之後,使用該金鑰的整合將無法再連接。", "Stop requiring single sign-on before removing a verified domain.": "請先停止強制使用單一登入,再移除已驗證的網域。", + "Google Tag Manager containers can only be added from the dashboard, not with an API key.": "Google 代碼管理工具容器只能在控制台中新增,不能透過 API 金鑰新增。", "You signed in without single sign-on": "您在未使用單一登入的情況下登入了", "If this wasn't you, change your password and set up two-factor authentication again with a new authenticator straight away.": "如果不是您本人操作,請立即更改密碼,並使用新的驗證器應用程式重新設定雙重驗證。", "This user owns an account that requires single sign-on. Two-factor authentication is their backup sign-in, so ask them to stop requiring single sign-on first.": "此用戶擁有一個強制使用單一登入的帳戶。雙重驗證是其後備登入方式,請先讓其停止強制使用單一登入。" diff --git a/backend/resources/api-reference/openapi.json b/backend/resources/api-reference/openapi.json new file mode 100644 index 0000000000..2e4bc4b444 --- /dev/null +++ b/backend/resources/api-reference/openapi.json @@ -0,0 +1,38833 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Hi.Events", + "description": "The Hi.Events API powers event management, ticketing, and checkout.\n\n## Authentication\n\nManagement endpoints require a bearer token in the `Authorization` header. Endpoints under `/public/*` — event pages, checkout, ticket lookup, check-in — require no authentication and are marked *(public)*.\n\n### API keys\n\nFor integrations, create an API key under **Settings → API keys** and send it with every request:\n\n```bash\ncurl https://your-hi-events-host/api/events \\\n -H \"Authorization: Bearer hie_…\" \\\n -H \"Accept: application/json\"\n```\n\n- A key acts as the person who created it, in the one account it was created in, with that person's role, permissions and event access. It never has more access than its owner, and changes to the owner's role apply straight away.\n- **Read and write** keys can call every endpoint the owner can. **Read-only** keys can call `GET` endpoints and exports; other requests return `403` with `error_code: API_KEY_READ_ONLY`.\n- Keys can't sign in, or manage the user's profile, sign-in security, team, API keys, payouts or account settings. Those endpoints return `403` with `error_code: API_KEY_NOT_ALLOWED`.\n- An expired, revoked or unknown key returns `401`. A key is revoked when its owner revokes it, when an admin revokes it, when the owner is deactivated or leaves the account, and when the owner's password is reset or their single sign-on changes.\n- The full key is shown once, when it's created. Hi.Events stores only a hash of it.\n\n### Session tokens\n\nThe Hi.Events web app uses a JWT obtained from `POST /auth/login`. It works on every management endpoint, including the ones API keys can't use.\n\n### Two-factor authentication\n\nWhen the user has two-factor authentication turned on, `POST /auth/login` returns `200` with `token: null`, `two_factor_required: true` and a `two_factor_challenge_token` that is valid for 10 minutes. Complete the login with `POST /auth/login/two-factor`, sending the challenge token plus either a 6-digit `code` from the user's authenticator app or a `recovery_code`. A wrong code returns `422`; an expired challenge, or too many wrong codes, returns `401` with `error_code: TWO_FACTOR_CHALLENGE_EXPIRED` and the login must start again. Users with several accounts verify once, then call `POST /auth/login/two-factor` again with the same challenge token and an `account_id`.\n\nTokens work the same once issued: two-factor authentication is only checked at login.\n\nIf an account requires two-factor authentication and the user has not set it up, authenticated endpoints return `403` with `error_code: TWO_FACTOR_SETUP_REQUIRED` until they do. `GET /users/me`, `GET /accounts`, the `/auth/*` endpoints and the `/users/me/two-factor/*` setup endpoints stay available.\n\n## Errors\n\nErrors return JSON with a `message`. Validation errors (`422`) also include an `errors` object keyed by field. Access errors include an `error_code`:\n\n| Status | `error_code` | Meaning |\n| --- | --- | --- |\n| `401` | — | Missing, invalid or expired token, or an invalid, expired or revoked API key |\n| `401` | `SESSION_REVOKED` | The session token was signed out everywhere, for example after a password reset |\n| `401` | `SSO_REQUIRED` | The user's organization requires single sign-on; sign in again through it |\n| `401` | `SSO_SESSION_EXPIRED` | A session started through single sign-on can't be refreshed; sign in again |\n| `403` | `API_KEY_READ_ONLY` | A read-only API key tried to make a change |\n| `403` | `API_KEY_NOT_ALLOWED` | The endpoint can't be used with an API key |\n| `403` | `READ_ONLY_ROLE` | The user has view-only access to the account |\n| `403` | `PERMISSION_REQUIRED` | The user's role lacks a permission the endpoint needs (for example refunds or financial data) |\n| `403` | `ACCESS_RESTRICTED` | The user only has access to some organizers or events, and this isn't one of them |\n| `403` | `TWO_FACTOR_SETUP_REQUIRED` | The account requires two-factor authentication and the user hasn't set it up |\n| `403` | `ACCOUNT_PENDING_DELETION` | The account is scheduled for deletion |\n| `403` | `FEATURE_UNAVAILABLE` | The feature isn't available on this installation or plan |\n| `429` | — | Too many requests; wait for the number of seconds in the `Retry-After` header |\n\n## Rate limits\n\nManagement endpoints are rate limited per user, shared between the web app and all of that user's API keys. The default is 180 requests a minute; self-hosted installations can change it with `APP_API_RATE_LIMIT_PER_MINUTE`.\n\n## Stability\n\nThe API isn't versioned yet. Endpoints that accept API keys are the integration surface, and we keep them backward compatible: new endpoints, optional request fields and response fields can be added at any time, so ignore fields you don't recognise. Removing or renaming an endpoint or field, or changing its type or meaning, is a breaking change; it's avoided where possible and always called out in the release notes. Endpoints marked *Not available to API keys* serve the Hi.Events web app and may change without notice.\n\n## Outgoing webhooks\n\nWebhooks are configured per event or organizer (see the *Webhooks* endpoints) and deliver a `POST` request to your URL for each subscribed event:\n\n```json\n{\n \"event_type\": \"order.created\",\n \"event_sent_at\": \"2026-08-07T10:00:00+00:00\",\n \"payload\": { }\n}\n```\n\n`payload` matches the schema of the corresponding resource (see the *Schemas* section):\n\n| Events | Payload schema |\n| --- | --- |\n| `order.created`, `order.updated`, `order.marked_as_paid`, `order.refunded`, `order.cancelled` | `OrderResource` |\n| `attendee.created`, `attendee.updated`, `attendee.cancelled` | `AttendeeResource` |\n| `product.created`, `product.updated`, `product.deleted` | `ProductResource` |\n| `event.created`, `event.updated`, `event.archived` | `EventResource` |\n| `checkin.created`, `checkin.deleted` | `AttendeeCheckInResource` |\n| `occurrence.cancelled` | `EventOccurrenceResource` |\n\n`email` on an order payload is `null` for box office sales made without a buyer email. `order.created` and `order.cancelled` additionally emit an `attendee.created` / `attendee.cancelled` webhook for each attendee on the order. Deliveries are signed: the `Signature` header contains an HMAC-SHA256 hash of the JSON body using your webhook's secret." + }, + "security": [ + { + "http": [] + } + ], + "paths": { + "/accounts/{accountId}": { + "get": { + "operationId": "accounts.getAccountAction", + "summary": "Get Account", + "tags": [ + "Accounts" + ], + "parameters": [ + { + "name": "accountId", + "in": "path", + "required": true, + "description": "**Optional**", + "schema": { + "type": [ + "integer", + "null" + ], + "default": null + }, + "x-optional": true + } + ], + "responses": { + "200": { + "description": "`AccountResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AccountResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/affiliates": { + "post": { + "operationId": "affiliates.createAffiliateAction", + "summary": "Create Affiliate", + "tags": [ + "Affiliates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUpdateAffiliateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`AffiliateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AffiliateResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "affiliates.getAffiliatesAction", + "summary": "Get Affiliates", + "tags": [ + "Affiliates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Paginated set of `AffiliateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AffiliateResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/affiliates/{affiliateId}": { + "put": { + "operationId": "affiliates.updateAffiliateAction", + "summary": "Update Affiliate", + "tags": [ + "Affiliates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "affiliateId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAffiliateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`AffiliateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AffiliateResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "affiliates.getAffiliateAction", + "summary": "Get Affiliate", + "tags": [ + "Affiliates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "affiliateId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`AffiliateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AffiliateResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "affiliates.deleteAffiliateAction", + "summary": "Delete Affiliate", + "tags": [ + "Affiliates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "affiliateId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/affiliates/export": { + "post": { + "operationId": "affiliates.exportAffiliatesAction", + "summary": "Export Affiliates", + "tags": [ + "Affiliates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "File download", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/attendees": { + "post": { + "operationId": "attendees.createAttendeeAction", + "summary": "Create Attendee", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAttendeeRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`AttendeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "attendees.getAttendeesAction", + "summary": "Get Attendees", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated set of `AttendeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AttendeeResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/attendees/{attendeeId}": { + "get": { + "operationId": "attendees.getAttendeeAction", + "summary": "Get Attendee", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "attendeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`AttendeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "attendees.editAttendeeAction", + "summary": "Edit Attendee", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "attendeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditAttendeeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`AttendeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "patch": { + "operationId": "attendees.partialEditAttendeeAction", + "summary": "Partial Edit Attendee", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "attendeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartialEditAttendeeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`AttendeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/attendees/export": { + "post": { + "operationId": "attendees.exportAttendeesAction", + "summary": "Export Attendees", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "File download", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/attendees/{attendeeId}/resend-ticket": { + "post": { + "operationId": "attendees.resendAttendeeTicketAction", + "summary": "Resend Attendee Ticket", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "attendeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/attendees/{attendeePublicId}/check_in": { + "post": { + "operationId": "attendees.checkInAttendeeAction", + "summary": "Check In Attendee", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "attendeePublicId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckInAttendeeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`AttendeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}/attendees/{attendeeShortId}": { + "get": { + "operationId": "attendees.getAttendeeActionPublic", + "summary": "Get Attendee (public)", + "tags": [ + "Attendees" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "attendeeShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`AttendeeResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + } + }, + "security": [] + } + }, + "/events/{eventId}/box-offices": { + "post": { + "operationId": "actions.createBoxOfficeAction", + "summary": "Create Box Office", + "tags": [ + "Box Office" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertBoxOfficeRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`BoxOfficeWithPinResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/BoxOfficeWithPinResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "actions.getBoxOfficesAction", + "summary": "Get Box Offices", + "tags": [ + "Box Office" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Paginated set of `BoxOfficeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BoxOfficeResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/box-offices/{boxOfficeId}": { + "get": { + "operationId": "actions.getBoxOfficeAction", + "summary": "Get Box Office", + "tags": [ + "Box Office" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "boxOfficeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`BoxOfficeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/BoxOfficeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "actions.updateBoxOfficeAction", + "summary": "Update Box Office", + "tags": [ + "Box Office" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "boxOfficeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertBoxOfficeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`BoxOfficeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/BoxOfficeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "actions.deleteBoxOfficeAction", + "summary": "Delete Box Office", + "tags": [ + "Box Office" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "boxOfficeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/box-offices/{boxOfficeId}/stats": { + "get": { + "operationId": "actions.getBoxOfficeStatsAction", + "summary": "Get Box Office Stats", + "tags": [ + "Box Office" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "boxOfficeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "from", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "`BoxOfficeStatsResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/BoxOfficeStatsResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/box-offices/{boxOfficeId}/reset-pin": { + "post": { + "operationId": "actions.resetBoxOfficePinAction", + "summary": "Reset Box Office Pin", + "tags": [ + "Box Office" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "boxOfficeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`BoxOfficeWithPinResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/BoxOfficeWithPinResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/capacity-assignments": { + "post": { + "operationId": "capacityAssignments.createCapacityAssignmentAction", + "summary": "Create Capacity Assignment", + "tags": [ + "Capacity Assignments" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertCapacityAssignmentRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`CapacityAssignmentResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CapacityAssignmentResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "capacityAssignments.getCapacityAssignmentsAction", + "summary": "Get Capacity Assignments", + "tags": [ + "Capacity Assignments" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Array of `CapacityAssignmentResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CapacityAssignmentResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/capacity-assignments/{capacityAssignmentId}": { + "get": { + "operationId": "capacityAssignments.getCapacityAssignmentAction", + "summary": "Get Capacity Assignment", + "tags": [ + "Capacity Assignments" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "capacityAssignmentId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`CapacityAssignmentResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CapacityAssignmentResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "capacityAssignments.updateCapacityAssignmentAction", + "summary": "Update Capacity Assignment", + "tags": [ + "Capacity Assignments" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "capacityAssignmentId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertCapacityAssignmentRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`CapacityAssignmentResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CapacityAssignmentResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "capacityAssignments.deleteCapacityAssignmentAction", + "summary": "Delete Capacity Assignment", + "tags": [ + "Capacity Assignments" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "capacityAssignmentId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/check-in-lists": { + "post": { + "operationId": "checkInLists.createCheckInListAction", + "summary": "Create Check In List", + "tags": [ + "Check In Lists" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertCheckInListRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`CheckInListResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CheckInListResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "checkInLists.getCheckInListsAction", + "summary": "Get Check In Lists", + "tags": [ + "Check In Lists" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Paginated set of `CheckInListResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CheckInListResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/check-in-lists/{checkInListId}": { + "get": { + "operationId": "checkInLists.getCheckInListAction", + "summary": "Get Check In List", + "tags": [ + "Check In Lists" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "checkInListId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`CheckInListResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CheckInListResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "checkInLists.updateCheckInListAction", + "summary": "Update Check In List", + "tags": [ + "Check In Lists" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "checkInListId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertCheckInListRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`CheckInListResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CheckInListResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "checkInLists.deleteCheckInListAction", + "summary": "Delete Check In List", + "tags": [ + "Check In Lists" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "checkInListId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/check-in-lists/{checkInListShortId}": { + "get": { + "operationId": "public.getCheckInListPublicAction", + "summary": "Get Check In List (public)", + "tags": [ + "Check In Lists / Public" + ], + "parameters": [ + { + "name": "checkInListShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`CheckInListResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CheckInListResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/check-in-lists/{checkInListShortId}/stats": { + "get": { + "operationId": "public.getCheckInListStatsPublicAction", + "summary": "Get Check In List Stats (public)", + "tags": [ + "Check In Lists / Public" + ], + "parameters": [ + { + "name": "checkInListShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "event_occurrence_id", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`CheckInListStatsPublicResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CheckInListStatsPublicResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/check-in-lists/{checkInListShortId}/attendees": { + "get": { + "operationId": "public.getCheckInListAttendeesPublicAction", + "summary": "Get Check In List Attendees (public)", + "tags": [ + "Check In Lists / Public" + ], + "parameters": [ + { + "name": "checkInListShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Array of `AttendeeWithCheckInPublicResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AttendeeWithCheckInPublicResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/check-in-lists/{shortId}/attendees/{attendeePublicId}": { + "get": { + "operationId": "public.getCheckInListAttendeePublicAction", + "summary": "Get Check In List Attendee (public)", + "tags": [ + "Check In Lists / Public" + ], + "parameters": [ + { + "name": "shortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "attendeePublicId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`AttendeeWithCheckInPublicResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeWithCheckInPublicResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/check-in-lists/{checkInListShortId}/attendees/{attendeePublicId}/detail": { + "get": { + "operationId": "public.getCheckInListAttendeeDetailPublicAction", + "summary": "Get Check In List Attendee Detail (public)", + "tags": [ + "Check In Lists / Public" + ], + "parameters": [ + { + "name": "checkInListShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "attendeePublicId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`AttendeeDetailPublicResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeDetailPublicResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/check-in-lists/{checkInListUuid}/check-ins": { + "post": { + "operationId": "public.createAttendeeCheckInPublicAction", + "summary": "Create Attendee Check In (public)", + "tags": [ + "Check In Lists / Public" + ], + "parameters": [ + { + "name": "checkInListUuid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAttendeeCheckInPublicRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Array of `AttendeeCheckInPublicResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AttendeeCheckInPublicResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/check-in-lists/{checkInListShortId}/check-ins/{checkInShortId}": { + "delete": { + "operationId": "public.deleteAttendeeCheckInPublicAction", + "summary": "Delete Attendee Check In (public)", + "tags": [ + "Check In Lists / Public" + ], + "parameters": [ + { + "name": "checkInListShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "checkInShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/color-themes": { + "get": { + "operationId": "common.getColorThemesAction", + "summary": "Get Color Themes (public)", + "tags": [ + "Common" + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": [] + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "security": [] + } + }, + "/organizers/{organizerId}/email-templates": { + "get": { + "operationId": "emailTemplates.getOrganizerEmailTemplatesAction", + "summary": "Get Organizer Email Templates", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "template_type", + "in": "query", + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/EmailTemplateType" + }, + { + "type": "null" + } + ] + } + }, + { + "name": "include_inactive", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ] + } + } + ], + "responses": { + "200": { + "description": "Array of `EmailTemplateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EmailTemplateResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "emailTemplates.createOrganizerEmailTemplateAction", + "summary": "Create Organizer Email Template", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "201": { + "description": "`EmailTemplateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EmailTemplateResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/email-templates/defaults": { + "get": { + "operationId": "emailTemplates.getDefaultEmailTemplateAction", + "summary": "Get Default Email Template", + "tags": [ + "Email Templates" + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/email-templates/{templateId}": { + "put": { + "operationId": "emailTemplates.updateOrganizerEmailTemplateAction", + "summary": "Update Organizer Email Template", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "templateId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EmailTemplateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EmailTemplateResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "emailTemplates.deleteOrganizerEmailTemplateAction", + "summary": "Delete Organizer Email Template", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "templateId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/email-templates/preview": { + "post": { + "operationId": "emailTemplates.previewOrganizerEmailTemplateAction", + "summary": "Preview Organizer Email Template", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "body": { + "type": "string" + }, + "context": { + "type": "object", + "properties": { + "event": { + "type": "object", + "properties": { + "title": { + "type": "string", + "const": "Summer Music Festival 2024" + }, + "date": { + "type": "string", + "const": "April 25, 2029" + }, + "time": { + "type": "string", + "const": "7:00 PM" + }, + "end_date": { + "type": "string", + "const": "April 26, 2029" + }, + "end_time": { + "type": "string", + "const": "11:00 PM" + }, + "full_address": { + "type": "string", + "const": "3 Arena, North Wall Quay, Dublin 1, Ireland" + }, + "description": { + "type": "string", + "const": "Join us for an unforgettable evening of live music featuring top artists from around the world." + }, + "timezone": { + "type": "string", + "const": "UTC" + }, + "location_details": { + "type": "object", + "properties": { + "venue_name": { + "type": "string", + "const": "3 Arena" + }, + "address_line_1": { + "type": "string", + "const": "North Wall Quay" + }, + "address_line_2": { + "type": "string" + }, + "city": { + "type": "string", + "const": "Dublin" + }, + "state_or_region": { + "type": "string", + "const": "Dublin 1" + }, + "zip_or_postal_code": { + "type": "string", + "const": "D01 T0X4" + }, + "country": { + "type": "string", + "const": "IE" + } + }, + "required": [ + "venue_name", + "address_line_1", + "address_line_2", + "city", + "state_or_region", + "zip_or_postal_code", + "country" + ] + }, + "url": { + "type": "string", + "const": "https://example.com/event/123/summer-fest" + } + }, + "required": [ + "title", + "date", + "time", + "end_date", + "end_time", + "full_address", + "description", + "timezone", + "location_details", + "url" + ] + }, + "order": { + "type": "object", + "properties": { + "url": { + "type": "string", + "const": "https://example.com/order/ABC123" + }, + "number": { + "type": "string" + }, + "total": { + "type": "string", + "const": "$150.00" + }, + "date": { + "type": "string", + "const": "January 10, 2024" + }, + "first_name": { + "type": "string", + "const": "John" + }, + "last_name": { + "type": "string", + "const": "Smith" + }, + "email": { + "type": "string", + "const": "john@example.com" + }, + "is_awaiting_offline_payment": { + "type": "boolean" + }, + "is_offline_payment": { + "type": "boolean" + }, + "seats": { + "type": "string", + "const": "Stalls · C-14, Stalls · C-15" + }, + "locale": { + "type": "string" + }, + "currency": { + "type": "string", + "const": "USD" + } + }, + "required": [ + "url", + "number", + "total", + "date", + "first_name", + "last_name", + "email", + "is_awaiting_offline_payment", + "is_offline_payment", + "seats", + "locale", + "currency" + ] + }, + "organizer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "ACME Events Inc." + }, + "email": { + "type": "string", + "const": "contact@example.com" + } + }, + "required": [ + "name", + "email" + ] + }, + "settings": { + "type": "object", + "properties": { + "support_email": { + "type": "string", + "const": "support@example.com" + }, + "offline_payment_instructions": { + "type": "string", + "const": "Please transfer the total amount to the following bank account within 5 business days." + }, + "post_checkout_message": { + "type": "string", + "const": "Thank you for your purchase! We look forward to seeing you at the event." + } + }, + "required": [ + "support_email", + "offline_payment_instructions", + "post_checkout_message" + ] + }, + "occurrence": { + "type": "object", + "properties": { + "start_date": { + "type": "string", + "const": "April 25, 2029" + }, + "start_time": { + "type": "string", + "const": "7:00 PM" + }, + "end_date": { + "type": "string", + "const": "April 26, 2029" + }, + "end_time": { + "type": "string", + "const": "11:00 PM" + }, + "label": { + "type": "string", + "const": "Session A" + } + }, + "required": [ + "start_date", + "start_time", + "end_date", + "end_time", + "label" + ] + }, + "lineup": { + "type": "object", + "properties": { + "heading": { + "type": "string", + "const": "Lineup" + }, + "names": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "const": "Velvet Harbour" + }, + { + "type": "string", + "const": "The Northern Arcs" + }, + { + "type": "string", + "const": "Mira Solenne" + } + ], + "minItems": 3, + "maxItems": 3, + "additionalItems": false + }, + "slots": { + "type": "array", + "prefixItems": [ + { + "type": "object", + "properties": { + "time": { + "type": "string", + "const": "Fri 25 Apr · 9:30 PM" + }, + "title": { + "type": "string", + "const": "Velvet Harbour" + }, + "stage": { + "type": "string", + "const": "Main Stage" + } + }, + "required": [ + "time", + "title", + "stage" + ] + }, + { + "type": "object", + "properties": { + "time": { + "type": "string", + "const": "Sat 26 Apr · 10:00 PM" + }, + "title": { + "type": "string", + "const": "The Northern Arcs" + }, + "stage": { + "type": "string", + "const": "Main Stage" + } + }, + "required": [ + "time", + "title", + "stage" + ] + } + ], + "minItems": 2, + "maxItems": 2, + "additionalItems": false + }, + "url": { + "type": "string", + "const": "https://example.com/event/123/summer-fest#schedule" + } + }, + "required": [ + "heading", + "names", + "slots", + "url" + ] + }, + "event_location": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "is_online": { + "type": "boolean" + }, + "online_connection_details": { + "type": "null" + }, + "name": { + "type": "string", + "const": "3 Arena" + }, + "label": { + "type": "null" + }, + "formatted_address": { + "type": "string", + "const": "3 Arena, North Wall Quay, Dublin 1, Ireland" + }, + "latitude": { + "type": "number", + "const": 53.3478 + }, + "longitude": { + "type": "string" + }, + "structured_address": { + "type": "object", + "properties": { + "venue_name": { + "type": "string", + "const": "3 Arena" + }, + "address_line_1": { + "type": "string", + "const": "North Wall Quay" + }, + "address_line_2": { + "type": "string" + }, + "city": { + "type": "string", + "const": "Dublin" + }, + "state_or_region": { + "type": "string", + "const": "Dublin 1" + }, + "zip_or_postal_code": { + "type": "string", + "const": "D01 T0X4" + }, + "country": { + "type": "string", + "const": "IE" + } + }, + "required": [ + "venue_name", + "address_line_1", + "address_line_2", + "city", + "state_or_region", + "zip_or_postal_code", + "country" + ] + } + }, + "required": [ + "type", + "is_online", + "online_connection_details", + "name", + "label", + "formatted_address", + "latitude", + "longitude", + "structured_address" + ] + }, + "attendee": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "John Smith" + }, + "email": { + "type": "string", + "const": "john@example.com" + }, + "seat": { + "type": "string", + "const": "Stalls · C-14" + } + }, + "required": [ + "name", + "email", + "seat" + ] + }, + "ticket": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "VIP Pass" + }, + "price": { + "type": "string", + "const": "$75.00" + }, + "url": { + "type": "string", + "const": "https://example.com/ticket/XYZ789" + } + }, + "required": [ + "name", + "price", + "url" + ] + }, + "cancellation": { + "type": "object", + "properties": { + "refund_issued": { + "type": "boolean" + } + }, + "required": [ + "refund_issued" + ] + } + }, + "required": [ + "event", + "order", + "organizer", + "settings", + "occurrence", + "lineup", + "event_location", + "attendee", + "ticket", + "cancellation" + ] + } + }, + "required": [ + "subject", + "body", + "context" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/email-templates/tokens/{templateType}": { + "get": { + "operationId": "emailTemplates.getAvailableTokensAction", + "summary": "Get Available Tokens", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "templateType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tokens": { + "anyOf": [ + { + "type": "array", + "prefixItems": [ + { + "type": "object", + "properties": { + "token": { + "type": "string", + "const": "{{ lineup.url }}" + }, + "description": { + "type": "string", + "const": "Link to the full schedule on the event page" + }, + "example": { + "type": "string", + "const": "https://example.com/event/123/summer-fest#schedule" + } + }, + "required": [ + "token", + "description", + "example" + ] + } + ], + "minItems": 1, + "maxItems": 1, + "additionalItems": false + }, + { + "type": "array", + "prefixItems": [ + { + "type": "object", + "properties": { + "token": { + "type": "string", + "const": "{{ event.url }}" + }, + "description": { + "type": "string", + "const": "Link to the event homepage" + }, + "example": { + "type": "string", + "const": "https://example.com/event/123/summer-fest" + } + }, + "required": [ + "token", + "description", + "example" + ] + } + ], + "minItems": 1, + "maxItems": 1, + "additionalItems": false + } + ] + } + }, + "required": [ + "tokens" + ] + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "const": "Invalid template type" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/email-templates": { + "get": { + "operationId": "emailTemplates.getEventEmailTemplatesAction", + "summary": "Get Event Email Templates", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "template_type", + "in": "query", + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/EmailTemplateType" + }, + { + "type": "null" + } + ] + } + }, + { + "name": "include_inactive", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ] + } + } + ], + "responses": { + "200": { + "description": "Array of `EmailTemplateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EmailTemplateResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "emailTemplates.createEventEmailTemplateAction", + "summary": "Create Event Email Template", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "201": { + "description": "`EmailTemplateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EmailTemplateResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/email-templates/{templateId}": { + "put": { + "operationId": "emailTemplates.updateEventEmailTemplateAction", + "summary": "Update Event Email Template", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "templateId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EmailTemplateResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EmailTemplateResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "emailTemplates.deleteEventEmailTemplateAction", + "summary": "Delete Event Email Template", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "templateId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/email-templates/preview": { + "post": { + "operationId": "emailTemplates.previewEventEmailTemplateAction", + "summary": "Preview Event Email Template", + "tags": [ + "Email Templates" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "body": { + "type": "string" + }, + "context": { + "type": "object", + "properties": { + "event": { + "type": "object", + "properties": { + "title": { + "type": "string", + "const": "Summer Music Festival 2024" + }, + "date": { + "type": "string", + "const": "April 25, 2029" + }, + "time": { + "type": "string", + "const": "7:00 PM" + }, + "end_date": { + "type": "string", + "const": "April 26, 2029" + }, + "end_time": { + "type": "string", + "const": "11:00 PM" + }, + "full_address": { + "type": "string", + "const": "3 Arena, North Wall Quay, Dublin 1, Ireland" + }, + "description": { + "type": "string", + "const": "Join us for an unforgettable evening of live music featuring top artists from around the world." + }, + "timezone": { + "type": "string", + "const": "UTC" + }, + "location_details": { + "type": "object", + "properties": { + "venue_name": { + "type": "string", + "const": "3 Arena" + }, + "address_line_1": { + "type": "string", + "const": "North Wall Quay" + }, + "address_line_2": { + "type": "string" + }, + "city": { + "type": "string", + "const": "Dublin" + }, + "state_or_region": { + "type": "string", + "const": "Dublin 1" + }, + "zip_or_postal_code": { + "type": "string", + "const": "D01 T0X4" + }, + "country": { + "type": "string", + "const": "IE" + } + }, + "required": [ + "venue_name", + "address_line_1", + "address_line_2", + "city", + "state_or_region", + "zip_or_postal_code", + "country" + ] + }, + "url": { + "type": "string", + "const": "https://example.com/event/123/summer-fest" + } + }, + "required": [ + "title", + "date", + "time", + "end_date", + "end_time", + "full_address", + "description", + "timezone", + "location_details", + "url" + ] + }, + "order": { + "type": "object", + "properties": { + "url": { + "type": "string", + "const": "https://example.com/order/ABC123" + }, + "number": { + "type": "string" + }, + "total": { + "type": "string", + "const": "$150.00" + }, + "date": { + "type": "string", + "const": "January 10, 2024" + }, + "first_name": { + "type": "string", + "const": "John" + }, + "last_name": { + "type": "string", + "const": "Smith" + }, + "email": { + "type": "string", + "const": "john@example.com" + }, + "is_awaiting_offline_payment": { + "type": "boolean" + }, + "is_offline_payment": { + "type": "boolean" + }, + "seats": { + "type": "string", + "const": "Stalls · C-14, Stalls · C-15" + }, + "locale": { + "type": "string" + }, + "currency": { + "type": "string", + "const": "USD" + } + }, + "required": [ + "url", + "number", + "total", + "date", + "first_name", + "last_name", + "email", + "is_awaiting_offline_payment", + "is_offline_payment", + "seats", + "locale", + "currency" + ] + }, + "organizer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "ACME Events Inc." + }, + "email": { + "type": "string", + "const": "contact@example.com" + } + }, + "required": [ + "name", + "email" + ] + }, + "settings": { + "type": "object", + "properties": { + "support_email": { + "type": "string", + "const": "support@example.com" + }, + "offline_payment_instructions": { + "type": "string", + "const": "Please transfer the total amount to the following bank account within 5 business days." + }, + "post_checkout_message": { + "type": "string", + "const": "Thank you for your purchase! We look forward to seeing you at the event." + } + }, + "required": [ + "support_email", + "offline_payment_instructions", + "post_checkout_message" + ] + }, + "occurrence": { + "type": "object", + "properties": { + "start_date": { + "type": "string", + "const": "April 25, 2029" + }, + "start_time": { + "type": "string", + "const": "7:00 PM" + }, + "end_date": { + "type": "string", + "const": "April 26, 2029" + }, + "end_time": { + "type": "string", + "const": "11:00 PM" + }, + "label": { + "type": "string", + "const": "Session A" + } + }, + "required": [ + "start_date", + "start_time", + "end_date", + "end_time", + "label" + ] + }, + "lineup": { + "type": "object", + "properties": { + "heading": { + "type": "string", + "const": "Lineup" + }, + "names": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "const": "Velvet Harbour" + }, + { + "type": "string", + "const": "The Northern Arcs" + }, + { + "type": "string", + "const": "Mira Solenne" + } + ], + "minItems": 3, + "maxItems": 3, + "additionalItems": false + }, + "slots": { + "type": "array", + "prefixItems": [ + { + "type": "object", + "properties": { + "time": { + "type": "string", + "const": "Fri 25 Apr · 9:30 PM" + }, + "title": { + "type": "string", + "const": "Velvet Harbour" + }, + "stage": { + "type": "string", + "const": "Main Stage" + } + }, + "required": [ + "time", + "title", + "stage" + ] + }, + { + "type": "object", + "properties": { + "time": { + "type": "string", + "const": "Sat 26 Apr · 10:00 PM" + }, + "title": { + "type": "string", + "const": "The Northern Arcs" + }, + "stage": { + "type": "string", + "const": "Main Stage" + } + }, + "required": [ + "time", + "title", + "stage" + ] + } + ], + "minItems": 2, + "maxItems": 2, + "additionalItems": false + }, + "url": { + "type": "string", + "const": "https://example.com/event/123/summer-fest#schedule" + } + }, + "required": [ + "heading", + "names", + "slots", + "url" + ] + }, + "event_location": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "is_online": { + "type": "boolean" + }, + "online_connection_details": { + "type": "null" + }, + "name": { + "type": "string", + "const": "3 Arena" + }, + "label": { + "type": "null" + }, + "formatted_address": { + "type": "string", + "const": "3 Arena, North Wall Quay, Dublin 1, Ireland" + }, + "latitude": { + "type": "number", + "const": 53.3478 + }, + "longitude": { + "type": "string" + }, + "structured_address": { + "type": "object", + "properties": { + "venue_name": { + "type": "string", + "const": "3 Arena" + }, + "address_line_1": { + "type": "string", + "const": "North Wall Quay" + }, + "address_line_2": { + "type": "string" + }, + "city": { + "type": "string", + "const": "Dublin" + }, + "state_or_region": { + "type": "string", + "const": "Dublin 1" + }, + "zip_or_postal_code": { + "type": "string", + "const": "D01 T0X4" + }, + "country": { + "type": "string", + "const": "IE" + } + }, + "required": [ + "venue_name", + "address_line_1", + "address_line_2", + "city", + "state_or_region", + "zip_or_postal_code", + "country" + ] + } + }, + "required": [ + "type", + "is_online", + "online_connection_details", + "name", + "label", + "formatted_address", + "latitude", + "longitude", + "structured_address" + ] + }, + "attendee": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "John Smith" + }, + "email": { + "type": "string", + "const": "john@example.com" + }, + "seat": { + "type": "string", + "const": "Stalls · C-14" + } + }, + "required": [ + "name", + "email", + "seat" + ] + }, + "ticket": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "VIP Pass" + }, + "price": { + "type": "string", + "const": "$75.00" + }, + "url": { + "type": "string", + "const": "https://example.com/ticket/XYZ789" + } + }, + "required": [ + "name", + "price", + "url" + ] + }, + "cancellation": { + "type": "object", + "properties": { + "refund_issued": { + "type": "boolean" + } + }, + "required": [ + "refund_issued" + ] + } + }, + "required": [ + "event", + "order", + "organizer", + "settings", + "occurrence", + "lineup", + "event_location", + "attendee", + "ticket", + "cancellation" + ] + } + }, + "required": [ + "subject", + "body", + "context" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/generate": { + "post": { + "operationId": "eventOccurrences.generateOccurrencesAction", + "summary": "Generate Occurrences", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateOccurrencesRequest" + } + } + } + }, + "responses": { + "202": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "enum": [ + "Job is still in progress", + "Job completed successfully", + "Export file not found", + "Job failed", + "Job not found" + ] + }, + "status": { + "type": "string" + }, + "job_uuid": { + "type": "string" + } + }, + "required": [ + "message", + "status", + "job_uuid" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/generate/status": { + "get": { + "operationId": "eventOccurrences.getOccurrenceGenerationStatusAction", + "summary": "Get Occurrence Generation Status", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "enum": [ + "Job is still in progress", + "Job completed successfully", + "Export file not found", + "Job failed", + "Job not found" + ] + }, + "status": { + "type": "string" + }, + "job_uuid": { + "type": "string" + } + }, + "required": [ + "message", + "status", + "job_uuid" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/bulk-update": { + "post": { + "operationId": "eventOccurrences.bulkUpdateOccurrencesAction", + "summary": "Bulk Update Occurrences", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkUpdateOccurrencesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "updated_count": { + "type": "integer" + }, + "updated_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "integer" + } + } + ] + } + }, + "required": [ + "updated_count", + "updated_ids" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences": { + "post": { + "operationId": "eventOccurrences.createEventOccurrenceAction", + "summary": "Create Event Occurrence", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertEventOccurrenceRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventOccurrenceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventOccurrenceResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "eventOccurrences.getEventOccurrencesAction", + "summary": "Get Event Occurrences", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "include_stats", + "in": "query", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Paginated set of `EventOccurrenceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventOccurrenceResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/{occurrenceId}": { + "get": { + "operationId": "eventOccurrences.getEventOccurrenceAction", + "summary": "Get Event Occurrence", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventOccurrenceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventOccurrenceResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "eventOccurrences.updateEventOccurrenceAction", + "summary": "Update Event Occurrence", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertEventOccurrenceRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventOccurrenceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventOccurrenceResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "eventOccurrences.deleteEventOccurrenceAction", + "summary": "Delete Event Occurrence", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/{occurrenceId}/cancel": { + "post": { + "operationId": "eventOccurrences.cancelOccurrenceAction", + "summary": "Cancel Occurrence", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CancelOccurrenceRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventOccurrenceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventOccurrenceResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/{occurrenceId}/reactivate": { + "post": { + "operationId": "eventOccurrences.reactivateOccurrenceAction", + "summary": "Reactivate Occurrence", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventOccurrenceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventOccurrenceResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/{occurrenceId}/price-overrides": { + "put": { + "operationId": "eventOccurrences.upsertPriceOverrideAction", + "summary": "Upsert Price Override", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertPriceOverrideRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`ProductPriceOccurrenceOverrideResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProductPriceOccurrenceOverrideResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "eventOccurrences.getPriceOverridesAction", + "summary": "Get Price Overrides", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `ProductPriceOccurrenceOverrideResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductPriceOccurrenceOverrideResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/{occurrenceId}/price-overrides/{overrideId}": { + "delete": { + "operationId": "eventOccurrences.deletePriceOverrideAction", + "summary": "Delete Price Override", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "overrideId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/{occurrenceId}/product-visibility": { + "get": { + "operationId": "eventOccurrences.getProductVisibilityAction", + "summary": "Get Product Visibility", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `ProductOccurrenceVisibilityResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductOccurrenceVisibilityResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "eventOccurrences.updateProductVisibilityAction", + "summary": "Update Product Visibility", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProductVisibilityRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Array of `ProductOccurrenceVisibilityResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductOccurrenceVisibilityResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/{occurrenceId}/product-availability": { + "get": { + "operationId": "eventOccurrences.getOccurrenceProductAvailabilityAction", + "summary": "Get Occurrence Product Availability", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `OccurrenceProductAvailabilityResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OccurrenceProductAvailabilityResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}/occurrences": { + "get": { + "operationId": "eventOccurrences.getEventOccurrencesPublicAction", + "summary": "Get Event Occurrences (public)", + "tags": [ + "Event Occurrences" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "start_date_from", + "in": "query", + "description": "Only return occurrences starting on or after this date (ISO 8601).", + "schema": { + "type": "string" + } + }, + { + "name": "start_date_to", + "in": "query", + "description": "Only return occurrences starting on or before this date (ISO 8601).", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventOccurrenceResourcePublic" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/events/{eventId}/settings": { + "get": { + "operationId": "eventSettings.getEventSettingsAction", + "summary": "Get Event Settings", + "tags": [ + "Event Settings" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventSettingsResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSettingsResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "eventSettings.editEventSettingsAction", + "summary": "Edit Event Settings", + "tags": [ + "Event Settings" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEventSettingsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventSettingsResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSettingsResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "patch": { + "operationId": "eventSettings.partialEditEventSettingsAction", + "summary": "Partial Edit Event Settings", + "tags": [ + "Event Settings" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEventSettingsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventSettingsResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSettingsResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/settings/platform-fee-preview": { + "get": { + "operationId": "eventSettings.getPlatformFeePreviewAction", + "summary": "Get Platform Fee Preview", + "tags": [ + "Event Settings" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "price", + "in": "query", + "required": true, + "schema": { + "type": "number", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "`PlatformFeePreviewResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/PlatformFeePreviewResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events": { + "post": { + "operationId": "events.createEventAction", + "summary": "Create Event", + "tags": [ + "Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEventRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + } + } + }, + "get": { + "operationId": "events.getEventsAction", + "summary": "Get Events", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Paginated set of `EventResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}": { + "get": { + "operationId": "events.getEventAction", + "summary": "Get Event", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "events.updateEventAction", + "summary": "Update Event", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEventRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "events.deleteEventAction", + "summary": "Delete Event", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/event-location": { + "patch": { + "operationId": "events.updateEventLocationAction", + "summary": "Update Event Location", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEventLocationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/status": { + "put": { + "operationId": "events.updateEventStatusAction", + "summary": "Update Event Status", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEventStatusRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/deletion-status": { + "get": { + "operationId": "events.getEventDeletionStatusAction", + "summary": "Get Event Deletion Status", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "can_delete": { + "type": "boolean" + }, + "reason": { + "type": [ + "string", + "null" + ], + "enum": [ + "This event has completed orders. Please cancel or refund all orders before deleting.", + null + ] + } + }, + "required": [ + "can_delete", + "reason" + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/duplicate": { + "post": { + "operationId": "events.duplicateEventAction", + "summary": "Duplicate Event", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DuplicateEventRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}": { + "get": { + "operationId": "hiEvents.actions.events.getEventPublicAction_0", + "summary": "Get Event (public)", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "promo_code", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "product_ids", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "product_category_ids", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`EventResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + } + }, + "security": [] + } + }, + "/public/organizers/{organizerId}/events": { + "get": { + "operationId": "events.getOrganizerEventsPublicAction", + "summary": "Get Organizer Events (public)", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Paginated set of `EventResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventResourcePublic" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/products": { + "get": { + "operationId": "hiEvents.actions.events.getEventPublicAction_0", + "summary": "Get Event (public)", + "tags": [ + "Events" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + } + }, + "security": [] + } + }, + "/events/{eventId}/images": { + "post": { + "operationId": "images.createEventImageAction", + "summary": "Create Event Image", + "tags": [ + "Events / Images" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateEventImageRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`ImageResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ImageResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "images.getEventImagesAction", + "summary": "Get Event Images", + "tags": [ + "Events / Images" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `ImageResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/images/{imageId}": { + "delete": { + "operationId": "images.deleteEventImageAction", + "summary": "Delete Event Image", + "tags": [ + "Events / Images" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "imageId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/stats": { + "get": { + "operationId": "stats.getEventStatsAction", + "summary": "Get Event Stats", + "tags": [ + "Events / Stats" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrence_id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "date_range", + "in": "query", + "schema": { + "type": "string", + "default": "month" + } + } + ], + "responses": { + "200": { + "description": "`JsonResource`", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/JsonResource" + } + }, + "required": [ + "data" + ] + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/counts": { + "get": { + "operationId": "stats.getEventCountsAction", + "summary": "Get Event Counts", + "tags": [ + "Events / Stats" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`JsonResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/JsonResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/images": { + "post": { + "operationId": "images.createImageAction", + "summary": "Create Image", + "tags": [ + "Images" + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateImageRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`ImageResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ImageResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/images/{imageId}": { + "delete": { + "operationId": "images.deleteImageAction", + "summary": "Delete Image", + "tags": [ + "Images" + ], + "parameters": [ + { + "name": "imageId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/lifecycle-emails/unsubscribe/{token}": { + "post": { + "operationId": "lifecycleEmails.unsubscribeFromLifecycleEmailsAction", + "summary": "Unsubscribe From Lifecycle Emails (public)", + "tags": [ + "Lifecycle Emails" + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "404": { + "description": "" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/organizers/{organizerId}/profiles": { + "get": { + "operationId": "lineup.getProfilesAction", + "summary": "Get Profiles", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `ProfileResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProfileResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "lineup.createProfileAction", + "summary": "Create Profile", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertProfileRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`ProfileResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProfileResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/profiles/{profileId}": { + "put": { + "operationId": "lineup.updateProfileAction", + "summary": "Update Profile", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "profileId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertProfileRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`ProfileResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProfileResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "lineup.deleteProfileAction", + "summary": "Delete Profile", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "profileId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/profiles/{profileId}/images/sort": { + "post": { + "operationId": "lineup.sortProfileImagesAction", + "summary": "Sort Profile Images", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "profileId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SortProfileImagesRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup": { + "get": { + "operationId": "lineup.getEventLineupAction", + "summary": "Get Event Lineup", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventLineupResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventLineupResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/export": { + "get": { + "operationId": "lineup.exportEventLineupAction", + "summary": "Export Event Lineup", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "File download", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/import": { + "post": { + "operationId": "lineup.importLineupAction", + "summary": "Import Lineup", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportLineupRequest" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": [] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/entries": { + "post": { + "operationId": "lineup.createLineupEntryAction", + "summary": "Create Lineup Entry", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateLineupEntryRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`LineupEntryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/LineupEntryResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/entries/sort": { + "post": { + "operationId": "lineup.sortLineupEntriesAction", + "summary": "Sort Lineup Entries", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SortLineupEntriesRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/entries/{lineupEntryId}": { + "put": { + "operationId": "lineup.updateLineupEntryAction", + "summary": "Update Lineup Entry", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "lineupEntryId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLineupEntryRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`LineupEntryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/LineupEntryResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "lineup.deleteLineupEntryAction", + "summary": "Delete Lineup Entry", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "lineupEntryId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/spaces": { + "post": { + "operationId": "lineup.createEventSpaceAction", + "summary": "Create Event Space", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertEventSpaceRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`EventSpaceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSpaceResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/spaces/{spaceId}": { + "put": { + "operationId": "lineup.updateEventSpaceAction", + "summary": "Update Event Space", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "spaceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertEventSpaceRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventSpaceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSpaceResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "lineup.deleteEventSpaceAction", + "summary": "Delete Event Space", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "spaceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/schedule-items": { + "post": { + "operationId": "lineup.createScheduleItemAction", + "summary": "Create Schedule Item", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertScheduleItemRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`ScheduleItemResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ScheduleItemResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/lineup/schedule-items/{scheduleItemId}": { + "put": { + "operationId": "lineup.updateScheduleItemAction", + "summary": "Update Schedule Item", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "scheduleItemId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertScheduleItemRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`ScheduleItemResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ScheduleItemResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "lineup.deleteScheduleItemAction", + "summary": "Delete Schedule Item", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "scheduleItemId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}/lineup": { + "get": { + "operationId": "lineup.getEventLineupPublicAction", + "summary": "Get Event Lineup (public)", + "tags": [ + "Lineup" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventLineupResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventLineupResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + } + }, + "security": [] + } + }, + "/organizers/{organizerId}/locations": { + "get": { + "operationId": "locations.getLocationsAction", + "summary": "Get Locations", + "tags": [ + "Locations" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated set of `LocationResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocationResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "locations.createLocationAction", + "summary": "Create Location", + "tags": [ + "Locations" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertLocationRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`LocationResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/LocationResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/geo/status": { + "get": { + "operationId": "locations.getGeoStatusAction", + "summary": "Get Geo Status", + "tags": [ + "Locations" + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "available": { + "type": "boolean" + } + }, + "required": [ + "available" + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/locations/autocomplete": { + "get": { + "operationId": "locations.geoAutocompleteAction", + "summary": "Geo Autocomplete", + "tags": [ + "Locations" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "query", + "in": "query", + "required": true, + "description": "Address search term.", + "schema": { + "type": "string" + } + }, + { + "name": "locale", + "in": "query", + "description": "Locale for the returned suggestions.", + "schema": { + "type": "string" + } + }, + { + "name": "country", + "in": "query", + "description": "Two-letter country code used to bias the results.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Array of `GeoSuggestionResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GeoSuggestionResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "502": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "429": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/locations/places/{placeId}": { + "get": { + "operationId": "locations.geoPlaceDetailsAction", + "summary": "Geo Place Details", + "tags": [ + "Locations" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "placeId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "locale", + "in": "query", + "description": "Locale for the returned place details.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`GeoPlaceResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/GeoPlaceResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "502": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "429": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/locations/{locationId}": { + "put": { + "operationId": "locations.updateLocationAction", + "summary": "Update Location", + "tags": [ + "Locations" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "locationId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertLocationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`LocationResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/LocationResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "locations.deleteLocationAction", + "summary": "Delete Location", + "tags": [ + "Locations" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "locationId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/messages": { + "post": { + "operationId": "messages.sendMessageAction", + "summary": "Send Message", + "tags": [ + "Messages" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendMessageRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`MessageResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/MessageResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "429": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "messages.getMessagesAction", + "summary": "Get Messages", + "tags": [ + "Messages" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated set of `MessageResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/messages/{messageId}/cancel": { + "post": { + "operationId": "messages.cancelMessageAction", + "summary": "Cancel Message", + "tags": [ + "Messages" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "messageId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`MessageResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/MessageResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/messages/{messageId}/recipients": { + "get": { + "operationId": "messages.getMessageRecipientsAction", + "summary": "Get Message Recipients", + "tags": [ + "Messages" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "messageId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Paginated set of `OutgoingMessageResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OutgoingMessageResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/orders": { + "get": { + "operationId": "orders.getOrdersAction", + "summary": "Get Orders", + "tags": [ + "Orders" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Array of `OrderResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/orders/{orderId}": { + "get": { + "operationId": "orders.getOrderAction", + "summary": "Get Order", + "tags": [ + "Orders" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`OrderResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "orders.editOrderAction", + "summary": "Edit Order", + "tags": [ + "Orders" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditOrderRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`OrderResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/orders/{orderId}/resend_confirmation": { + "post": { + "operationId": "orders.resendOrderConfirmationAction", + "summary": "Resend Order Confirmation", + "tags": [ + "Orders" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "422": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/orders/{orderId}/cancel": { + "post": { + "operationId": "orders.cancelOrderAction", + "summary": "Cancel Order", + "tags": [ + "Orders" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "refund": { + "type": "boolean", + "default": false + } + } + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/orders/{orderId}/mark-as-paid": { + "post": { + "operationId": "orders.markOrderAsPaidAction", + "summary": "Mark Order As Paid", + "tags": [ + "Orders" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`OrderResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/orders/export": { + "post": { + "operationId": "orders.exportOrdersAction", + "summary": "Export Orders", + "tags": [ + "Orders" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "product_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer" + }, + "maxItems": 500 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "File download", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/orders/{orderId}/invoice": { + "get": { + "operationId": "orders.downloadOrderInvoiceAction", + "summary": "Download Order Invoice", + "tags": [ + "Orders" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Invoice PDF", + "content": { + "application/json": { + "schema": { + "type": "object" + } + }, + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/orders/{orderId}/refund": { + "post": { + "operationId": "payment.refundOrderAction", + "summary": "Refund Order", + "tags": [ + "Orders / Payment" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefundOrderRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`OrderResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}/order/{orderShortId}/stripe/payment_intent": { + "post": { + "operationId": "stripe.createPaymentIntentActionPublic", + "summary": "Create Payment Intent (public)", + "tags": [ + "Orders / Payment / Stripe" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "client_secret": { + "type": "string" + }, + "account_id": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "public_key": { + "type": [ + "string", + "null" + ] + }, + "stripe_platform": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "client_secret", + "account_id", + "public_key", + "stripe_platform" + ] + } + } + } + }, + "422": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + }, + "get": { + "operationId": "stripe.getPaymentIntentActionPublic", + "summary": "Get Payment Intent (public)", + "tags": [ + "Orders / Payment / Stripe" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "paymentIntentId": { + "type": "string" + }, + "amount": { + "type": "string" + } + }, + "required": [ + "status", + "paymentIntentId", + "amount" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order": { + "post": { + "operationId": "public.createOrderActionPublic", + "description": "`products.*.event_occurrence_id` is required for recurring events; for single-occurrence\nevents it is inferred when omitted. Product availability, capacity, and pricing are\nvalidated against the event's live configuration.", + "summary": "Create Order (public)", + "tags": [ + "Orders / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrderRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`OrderResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "object", + "properties": { + "unavailable_seat_uids": { + "type": "string" + } + }, + "required": [ + "unavailable_seat_uids" + ] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "403": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order/{orderShortId}": { + "put": { + "operationId": "public.completeOrderActionPublic", + "description": "`order.questions` and `products.*.questions` are validated against the questions configured\nfor the event. The `order.address` fields become required when the event's settings require\na billing address.", + "summary": "Complete Order (public)", + "tags": [ + "Orders / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CompleteOrderRequest" + }, + { + "type": "object", + "properties": { + "order": { + "type": "object", + "properties": { + "opted_into_marketing": { + "type": "boolean", + "default": false + } + } + } + } + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "`OrderResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + }, + "get": { + "operationId": "public.getOrderActionPublic", + "summary": "Get Order (public)", + "tags": [ + "Orders / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "session_identifier", + "in": "query", + "description": "Checkout session identifier issued when the order was created. Sets the checkout session cookie on the response.", + "schema": { + "type": "string" + } + }, + { + "name": "include", + "in": "query", + "description": "Comma-separated list of relations to include in the response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`OrderResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + }, + "patch": { + "operationId": "selfService.editOrderPublicAction", + "summary": "Edit Order (public)", + "tags": [ + "Self Service" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditOrderPublicRequest" + } + } + } + }, + "responses": { + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "const": "Order updated successfully" + }, + "new_short_id": { + "type": "string" + } + }, + "required": [ + "message", + "new_short_id" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order/{orderShortId}/abandon": { + "post": { + "operationId": "public.abandonOrderActionPublic", + "summary": "Abandon Order (public)", + "tags": [ + "Orders / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "200": { + "description": "`OrderResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order/{orderShortId}/await-offline-payment": { + "post": { + "operationId": "public.transitionOrderToOfflinePaymentPublicAction", + "summary": "Transition Order To Offline Payment (public)", + "tags": [ + "Orders / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`OrderResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrderResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order/{orderShortId}/invoice": { + "get": { + "operationId": "public.downloadOrderInvoicePublicAction", + "summary": "Download Order Invoice (public)", + "tags": [ + "Orders / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Invoice PDF", + "content": { + "application/json": { + "schema": { + "type": "object" + } + }, + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/organizers": { + "post": { + "operationId": "organizers.createOrganizerAction", + "summary": "Create Organizer", + "tags": [ + "Organizers" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertOrganizerRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`OrganizerResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + } + } + }, + "get": { + "operationId": "organizers.getOrganizersAction", + "summary": "Get Organizers", + "tags": [ + "Organizers" + ], + "responses": { + "200": { + "description": "Array of `OrganizerResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrganizerResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}": { + "post": { + "operationId": "organizers.editOrganizerAction", + "summary": "Edit Organizer", + "tags": [ + "Organizers" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertOrganizerRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`OrganizerResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "organizers.deleteOrganizerAction", + "summary": "Delete Organizer", + "tags": [ + "Organizers" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "organizers.getOrganizerAction", + "summary": "Get Organizer", + "tags": [ + "Organizers" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`OrganizerResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/status": { + "put": { + "operationId": "organizers.updateOrganizerStatusAction", + "summary": "Update Organizer Status", + "tags": [ + "Organizers" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOrganizerStatusRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`OrganizerResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/deletion-status": { + "get": { + "operationId": "organizers.getOrganizerDeletionStatusAction", + "summary": "Get Organizer Deletion Status", + "tags": [ + "Organizers" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "can_delete": { + "type": "boolean" + }, + "reason": { + "type": [ + "string", + "null" + ], + "enum": [ + "This organizer has events with completed orders. Please cancel or refund all orders first.", + "You cannot delete the last organizer on your account.", + null + ] + } + }, + "required": [ + "can_delete", + "reason" + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/events": { + "get": { + "operationId": "organizers.getOrganizerEventsAction", + "summary": "Get Organizer Events", + "tags": [ + "Organizers" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated set of `EventResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/location": { + "patch": { + "operationId": "organizers.updateOrganizerLocationAction", + "summary": "Update Organizer Location", + "tags": [ + "Organizers" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOrganizerLocationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`OrganizerResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/organizers/{organizerId}": { + "get": { + "operationId": "organizers.getPublicOrganizerAction", + "summary": "Get Organizer (public)", + "tags": [ + "Organizers" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`OrganizerResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + } + }, + "security": [] + } + }, + "/organizers/{organizerId}/orders": { + "get": { + "operationId": "orders.getOrganizerOrdersAction", + "summary": "Get Organizer Orders", + "tags": [ + "Organizers / Orders" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Array of `OrderResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/organizers/{organizerId}/contact": { + "post": { + "operationId": "public.sendOrganizerContactMessagePublicAction", + "summary": "Send Organizer Contact Message (public)", + "tags": [ + "Organizers / Public" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 255 + }, + "message": { + "type": "string", + "maxLength": 5000 + } + }, + "required": [ + "name", + "email", + "message" + ] + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "const": "Message sent successfully" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/organizers/{organizerId}/settings": { + "get": { + "operationId": "settings.getOrganizerSettingsAction", + "summary": "Get Organizer Settings", + "tags": [ + "Organizers / Settings" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`OrganizerSettingsResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerSettingsResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "patch": { + "operationId": "settings.partialUpdateOrganizerSettingsAction", + "summary": "Partial Update Organizer Settings", + "tags": [ + "Organizers / Settings" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartialUpdateOrganizerSettingsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`OrganizerSettingsResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerSettingsResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/stats": { + "get": { + "operationId": "stats.getOrganizerStatsAction", + "summary": "Get Organizer Stats", + "tags": [ + "Organizers / Stats" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "date_range", + "in": "query", + "schema": { + "type": "string", + "default": "month" + } + }, + { + "name": "currency_code", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/OrganizerStatsResponseDTO" + } + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/vat-settings": { + "get": { + "operationId": "vat.getOrganizerVatSettingAction", + "summary": "Get Organizer Vat Setting", + "tags": [ + "Organizers / Vat" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`OrganizerVatSettingResource`", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizerVatSettingResource" + } + }, + "required": [ + "data" + ] + }, + { + "type": "object", + "properties": { + "data": { + "type": "null" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/webhooks": { + "post": { + "operationId": "webhooks.createOrganizerWebhookAction", + "summary": "Create Organizer Webhook", + "tags": [ + "Organizers / Webhooks" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertWebhookRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`WebhookResourceWithSecret`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WebhookResourceWithSecret" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "webhooks.getOrganizerWebhooksAction", + "summary": "Get Organizer Webhooks", + "tags": [ + "Organizers / Webhooks" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `WebhookResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/webhooks/{webhookId}": { + "put": { + "operationId": "webhooks.editOrganizerWebhookAction", + "summary": "Edit Organizer Webhook", + "tags": [ + "Organizers / Webhooks" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertWebhookRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`WebhookResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WebhookResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "webhooks.getOrganizerWebhookAction", + "summary": "Get Organizer Webhook", + "tags": [ + "Organizers / Webhooks" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`WebhookResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WebhookResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "webhooks.deleteOrganizerWebhookAction", + "summary": "Delete Organizer Webhook", + "tags": [ + "Organizers / Webhooks" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/webhooks/{webhookId}/logs": { + "get": { + "operationId": "webhooks.getOrganizerWebhookLogsAction", + "summary": "Get Organizer Webhook Logs", + "tags": [ + "Organizers / Webhooks" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `WebhookLogResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookLogResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/product-categories": { + "post": { + "operationId": "productCategories.createProductCategoryAction", + "summary": "Create Product Category", + "tags": [ + "Product Categories" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertProductCategoryRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`ProductCategoryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProductCategoryResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "productCategories.getProductCategoriesAction", + "summary": "Get Product Categories", + "tags": [ + "Product Categories" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `ProductCategoryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductCategoryResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/product-categories/{productCategoryId}": { + "get": { + "operationId": "productCategories.getProductCategoryAction", + "summary": "Get Product Category", + "tags": [ + "Product Categories" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "productCategoryId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`ProductCategoryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProductCategoryResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "productCategories.editProductCategoryAction", + "summary": "Edit Product Category", + "tags": [ + "Product Categories" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "productCategoryId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertProductCategoryRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`ProductCategoryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProductCategoryResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "productCategories.deleteProductCategoryAction", + "summary": "Delete Product Category", + "tags": [ + "Product Categories" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "productCategoryId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/products": { + "post": { + "operationId": "products.createProductAction", + "summary": "Create Product", + "tags": [ + "Products" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertProductRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`ProductResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProductResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "products.getProductsAction", + "summary": "Get Products", + "tags": [ + "Products" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Paginated set of `ProductResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/products/sort": { + "post": { + "operationId": "products.sortProductsAction", + "summary": "Sort Products", + "tags": [ + "Products" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SortProductsRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/products/{productId}": { + "put": { + "operationId": "products.editProductAction", + "summary": "Edit Product", + "tags": [ + "Products" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "productId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertProductRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`ProductResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProductResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "products.getProductAction", + "summary": "Get Product", + "tags": [ + "Products" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "productId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`ProductResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProductResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "products.deleteProductAction", + "summary": "Delete Product", + "tags": [ + "Products" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "productId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/products/purchases/export": { + "post": { + "operationId": "purchases.exportProductPurchasesAction", + "summary": "Export Product Purchases", + "tags": [ + "Products / Purchases" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExportProductPurchasesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "File download", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/products/{productId}/purchases": { + "get": { + "operationId": "purchases.getProductPurchasesAction", + "summary": "Get Product Purchases", + "tags": [ + "Products / Purchases" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "productId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Array of `ProductPurchaseResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductPurchaseResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/products/{productId}/purchases/summary": { + "get": { + "operationId": "purchases.getProductPurchaseSummaryAction", + "summary": "Get Product Purchase Summary", + "tags": [ + "Products / Purchases" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "productId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "event_occurrence_id", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ] + } + } + ], + "responses": { + "200": { + "description": "`ProductPurchaseSummaryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ProductPurchaseSummaryResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/promo-codes": { + "post": { + "operationId": "promoCodes.createPromoCodeAction", + "summary": "Create Promo Code", + "tags": [ + "Promo Codes" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUpdatePromoCodeRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`PromoCodeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/PromoCodeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "promoCodes.getPromoCodesAction", + "summary": "Get Promo Codes", + "tags": [ + "Promo Codes" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `PromoCodeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PromoCodeResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/promo-codes/{promoCodeId}": { + "put": { + "operationId": "promoCodes.updatePromoCodeAction", + "summary": "Update Promo Code", + "tags": [ + "Promo Codes" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "promoCodeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUpdatePromoCodeRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`PromoCodeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/PromoCodeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "promoCodes.getPromoCodeAction", + "summary": "Get Promo Code", + "tags": [ + "Promo Codes" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "promoCodeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`PromoCodeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/PromoCodeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "promoCodes.deletePromoCodeAction", + "summary": "Delete Promo Code", + "tags": [ + "Promo Codes" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "promoCodeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}/promo-codes/{promoCode}": { + "get": { + "operationId": "promoCodes.getPromoCodePublic", + "summary": "Get Promo Code (public)", + "tags": [ + "Promo Codes" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "promoCode", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "discount": { + "type": "string" + }, + "discount_type": { + "type": "string" + }, + "discount_applies_to": { + "type": "string" + }, + "applies_to_all_products": { + "type": "string" + } + }, + "required": [ + "valid", + "discount", + "discount_type", + "discount_applies_to", + "applies_to_all_products" + ] + }, + { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + } + }, + "required": [ + "valid" + ] + } + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/events/{eventId}/questions": { + "post": { + "operationId": "questions.createQuestionAction", + "description": "⚠️ Cannot generate request documentation: Cannot use \"::class\" on array", + "summary": "Create Question", + "tags": [ + "Questions" + ], + "responses": { + "201": { + "description": "`QuestionResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/QuestionResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "questions.getQuestionsAction", + "summary": "Get Questions", + "tags": [ + "Questions" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `QuestionResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/questions/{questionId}": { + "put": { + "operationId": "questions.editQuestionAction", + "description": "⚠️ Cannot generate request documentation: Cannot use \"::class\" on array", + "summary": "Edit Question", + "tags": [ + "Questions" + ], + "responses": { + "200": { + "description": "`QuestionResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/QuestionResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "questions.getQuestionAction", + "summary": "Get Question", + "tags": [ + "Questions" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "questionId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`QuestionResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/QuestionResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "questions.deleteQuestionAction", + "summary": "Delete Question", + "tags": [ + "Questions" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "questionId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/questions/sort": { + "post": { + "operationId": "questions.sortQuestionsAction", + "summary": "Sort Questions", + "tags": [ + "Questions" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SortQuestionsRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No content" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/questions/{questionId}/answers/{questionAnswerId}": { + "put": { + "operationId": "questions.editQuestionAnswerAction", + "summary": "Edit Question Answer", + "tags": [ + "Questions" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "questionId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "questionAnswerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditQuestionAnswerRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/questions/answers/export": { + "get": { + "operationId": "questions.exportQuestionAnswersAction", + "summary": "Export Question Answers", + "tags": [ + "Questions" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "message": { + "type": "string", + "const": "Job started successfully" + }, + "status": { + "type": "string" + }, + "job_uuid": { + "type": "string" + } + }, + "required": [ + "message", + "status", + "job_uuid" + ] + }, + { + "type": "object", + "properties": { + "message": { + "type": "string", + "enum": [ + "Job is still in progress", + "Job completed successfully", + "Export file not found", + "Job failed", + "Job not found" + ] + }, + "status": { + "type": "string" + }, + "job_uuid": { + "type": "string" + }, + "download_url": { + "type": "null" + } + }, + "required": [ + "message", + "status", + "job_uuid", + "download_url" + ] + } + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}/questions": { + "get": { + "operationId": "questions.getQuestionsPublicAction", + "summary": "Get Questions (public)", + "tags": [ + "Questions" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `QuestionResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionResourcePublic" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/organizers/{organizerId}/reports/{reportType}": { + "get": { + "operationId": "reports.getOrganizerReportAction", + "description": "The response rows depend on the requested `reportType`. Paginated report types return\n`{data, meta, links}`; non-paginated report types return `{data}`.", + "summary": "Get Organizer Report", + "tags": [ + "Reports" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "reportType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "start_date", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "end_date", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "currency", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "minLength": 3, + "maxLength": 3 + } + }, + { + "name": "event_id", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ] + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 1000 + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedReportDTO" + }, + { + "type": "array", + "items": [] + } + ] + } + }, + "required": [ + "data" + ] + }, + { + "anyOf": [ + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": [] + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "page": { + "type": "integer", + "const": 1 + }, + "per_page": { + "type": "integer", + "const": 1000 + }, + "last_page": { + "type": "integer" + } + }, + "required": [ + "total", + "page", + "per_page", + "last_page" + ] + } + }, + "required": [ + "data", + "pagination" + ] + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": [] + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "last_page": { + "type": "integer" + } + }, + "required": [ + "total", + "page", + "per_page", + "last_page" + ] + } + }, + "required": [ + "data", + "pagination" + ] + }, + { + "type": "array", + "items": [] + } + ] + } + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/reports/{reportType}/export": { + "get": { + "operationId": "reports.exportOrganizerReportAction", + "summary": "Export Organizer Report", + "tags": [ + "Reports" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "reportType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "start_date", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "end_date", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "currency", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "minLength": 3, + "maxLength": 3 + } + }, + { + "name": "event_id", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ] + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 1000 + } + } + ], + "responses": { + "200": { + "description": "File download", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/reports/{reportType}": { + "get": { + "operationId": "reports.getReportAction", + "summary": "Get Report", + "tags": [ + "Reports" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "reportType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "start_date", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "end_date", + "in": "query", + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "occurrence_id", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ] + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": [] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/seat-maps": { + "get": { + "operationId": "actions.getSeatMapsAction", + "summary": "Get Seat Maps", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated set of `SeatMapPreviewResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeatMapPreviewResource" + } + }, + "links": { + "type": "object", + "properties": { + "first": { + "type": [ + "string", + "null" + ] + }, + "last": { + "type": [ + "string", + "null" + ] + }, + "prev": { + "type": [ + "string", + "null" + ] + }, + "next": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "first", + "last", + "prev", + "next" + ] + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer", + "minimum": 1 + }, + "from": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "last_page": { + "type": "integer", + "minimum": 1 + }, + "links": { + "type": "array", + "description": "Generated paginator links.", + "items": { + "type": "object", + "properties": { + "url": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "label", + "active" + ] + } + }, + "path": { + "type": [ + "string", + "null" + ], + "description": "Base path for paginator generated URLs." + }, + "per_page": { + "type": "integer", + "description": "Number of items shown per page.", + "minimum": 0 + }, + "to": { + "type": [ + "integer", + "null" + ], + "description": "Number of the last item in the slice.", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total number of items being paginated.", + "minimum": 0 + } + }, + "required": [ + "current_page", + "from", + "last_page", + "links", + "path", + "per_page", + "to", + "total" + ] + } + }, + "required": [ + "data", + "links", + "meta" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "actions.createSeatMapAction", + "summary": "Create Seat Map", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertSeatMapRequest" + } + } + } + }, + "responses": { + "201": { + "description": "`SeatMapResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SeatMapResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/seat-maps/{seatMapId}": { + "get": { + "operationId": "actions.getSeatMapAction", + "summary": "Get Seat Map", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "seatMapId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`SeatMapResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SeatMapResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "actions.updateSeatMapAction", + "summary": "Update Seat Map", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "seatMapId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertSeatMapRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`SeatMapResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SeatMapResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "actions.deleteSeatMapAction", + "summary": "Delete Seat Map", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "seatMapId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/seat-map": { + "get": { + "operationId": "actions.getEventSeatMapAction", + "summary": "Get Event Seat Map", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventSeatMapResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSeatMapResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "actions.attachEventSeatMapAction", + "summary": "Attach Event Seat Map", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "seat_map_id": { + "type": "integer" + } + }, + "required": [ + "seat_map_id" + ] + } + } + } + }, + "responses": { + "201": { + "description": "`EventSeatMapResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSeatMapResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "actions.detachEventSeatMapAction", + "summary": "Detach Event Seat Map", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/seat-map/layout": { + "put": { + "operationId": "actions.updateEventSeatMapLayoutAction", + "summary": "Update Event Seat Map Layout", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEventSeatMapLayoutRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventSeatMapResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSeatMapResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + }, + { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 0, + "maxItems": 0, + "additionalItems": false + }, + "requires_relabel_confirmation": { + "type": "boolean" + }, + "relabelled_seat_labels": { + "type": "string" + } + }, + "required": [ + "message", + "errors", + "requires_relabel_confirmation", + "relabelled_seat_labels" + ] + } + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/seat-map/band-products": { + "put": { + "operationId": "actions.updateEventSeatMapBandProductsAction", + "summary": "Update Event Seat Map Band Products", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEventSeatMapBandProductsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`EventSeatMapResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSeatMapResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/seat-map/rules": { + "put": { + "operationId": "actions.updateEventSeatMapRulesAction", + "summary": "Update Event Seat Map Rules", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "prevent_orphan_seats": { + "type": "boolean" + }, + "max_seats_per_order": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 100 + }, + "allow_seat_change": { + "type": "boolean" + } + }, + "required": [ + "prevent_orphan_seats", + "max_seats_per_order", + "allow_seat_change" + ] + } + } + } + }, + "responses": { + "200": { + "description": "`EventSeatMapResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSeatMapResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/seat-map/sync-from-source": { + "post": { + "operationId": "actions.syncEventSeatMapFromSourceAction", + "summary": "Sync Event Seat Map From Source", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "dry_run": { + "type": "boolean" + } + }, + "required": [ + "dry_run" + ] + } + } + } + }, + "responses": { + "200": { + "description": "`SeatMapDiffResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SeatMapDiffResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/occurrences/{occurrenceId}/occupied-seats": { + "get": { + "operationId": "actions.getOccupiedSeatsAction", + "summary": "Get Occupied Seats", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `OccupiedSeatResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OccupiedSeatResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/seat-blocks": { + "post": { + "operationId": "actions.blockSeatsAction", + "summary": "Block Seats", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SeatBlockRequest" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "blocked": { + "type": "integer", + "minimum": 0 + }, + "skipped": { + "type": "array", + "items": [] + } + }, + "required": [ + "blocked", + "skipped" + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/seat-blocks/release": { + "post": { + "operationId": "actions.releaseSeatBlocksAction", + "summary": "Release Seat Blocks", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SeatBlockRequest" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "released": { + "type": "integer" + } + }, + "required": [ + "released" + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/attendees/{attendeeId}/seat": { + "put": { + "operationId": "actions.moveAttendeeSeatAction", + "summary": "Move Attendee Seat", + "tags": [ + "Seating" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "attendeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveAttendeeSeatRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`AttendeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "object", + "properties": { + "unavailable_seat_uids": { + "type": "string" + } + }, + "required": [ + "unavailable_seat_uids" + ] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}/seat-map": { + "get": { + "operationId": "public.getEventSeatMapPublicAction", + "summary": "Get Event Seat Map (public)", + "tags": [ + "Seating / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`EventSeatMapResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/EventSeatMapResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + } + }, + "security": [] + } + }, + "/public/events/{eventId}/occurrences/{occurrenceId}/seat-availability": { + "get": { + "operationId": "public.getSeatAvailabilityPublicAction", + "summary": "Get Seat Availability (public)", + "tags": [ + "Seating / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`SeatAvailabilityResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SeatAvailabilityResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/occurrences/{occurrenceId}/best-available-seats": { + "get": { + "operationId": "public.getBestAvailableSeatsPublicAction", + "summary": "Get Best Available Seats (public)", + "tags": [ + "Seating / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "occurrenceId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "product_id", + "in": "query", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "quantity", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 20 + } + }, + { + "name": "accessible", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "exclude[]", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string", + "maxLength": 24 + } + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "seat_uids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "seat_uids" + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order/{orderShortId}/attendees/{attendeeShortId}/seat": { + "put": { + "operationId": "public.changeAttendeeSeatPublicAction", + "summary": "Change Attendee Seat (public)", + "tags": [ + "Seating / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "attendeeShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveAttendeeSeatRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`AttendeeResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AttendeeResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "object", + "properties": { + "unavailable_seat_uids": { + "type": "string" + } + }, + "required": [ + "unavailable_seat_uids" + ] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order/{orderShortId}/resend-confirmation": { + "post": { + "operationId": "selfService.resendOrderConfirmationPublicAction", + "summary": "Resend Order Confirmation (public)", + "tags": [ + "Self Service" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "const": "Order confirmation resent successfully" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order/{orderShortId}/attendees/{attendeeShortId}": { + "patch": { + "operationId": "selfService.editAttendeePublicAction", + "summary": "Edit Attendee (public)", + "tags": [ + "Self Service" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "attendeeShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditAttendeePublicRequest" + } + } + } + }, + "responses": { + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "const": "Attendee updated successfully" + }, + "new_short_id": { + "type": "string" + } + }, + "required": [ + "message", + "new_short_id" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/order/{orderShortId}/attendees/{attendeeShortId}/resend-ticket": { + "post": { + "operationId": "selfService.resendAttendeeTicketPublicAction", + "summary": "Resend Attendee Ticket (public)", + "tags": [ + "Self Service" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "orderShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "attendeeShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "const": "Ticket resent successfully" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/accounts/{accountId}/taxes-and-fees": { + "post": { + "operationId": "taxesAndFees.createTaxOrFeeAction", + "summary": "Create Tax Or Fee", + "tags": [ + "Taxes And Fees" + ], + "parameters": [ + { + "name": "accountId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTaxOrFeeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`TaxAndFeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/TaxAndFeeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "taxesAndFees.getTaxOrFeeAction", + "summary": "Get Tax Or Fee", + "tags": [ + "Taxes And Fees" + ], + "parameters": [ + { + "name": "accountId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `TaxAndFeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaxAndFeeResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/accounts/{accountId}/taxes-and-fees/{taxId}": { + "put": { + "operationId": "taxesAndFees.editTaxOrFeeAction", + "summary": "Edit Tax Or Fee", + "tags": [ + "Taxes And Fees" + ], + "parameters": [ + { + "name": "accountId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "taxId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTaxOrFeeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`TaxAndFeeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/TaxAndFeeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/accounts/{accountId}/taxes-and-fees/{taxOrFeeId}": { + "delete": { + "operationId": "taxesAndFees.deleteTaxOrFeeAction", + "summary": "Delete Tax Or Fee", + "tags": [ + "Taxes And Fees" + ], + "parameters": [ + { + "name": "accountId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "taxOrFeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/ticket-lookup": { + "post": { + "operationId": "ticketLookup.sendTicketLookupEmailAction", + "summary": "Send Ticket Lookup Email (public)", + "tags": [ + "Ticket Lookup" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendTicketLookupEmailRequest" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "const": "If you have tickets associated with this email, we will send you an email with the details." + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/ticket-lookup/{token}": { + "get": { + "operationId": "ticketLookup.getOrdersByLookupTokenAction", + "summary": "Get Orders By Lookup Token (public)", + "tags": [ + "Ticket Lookup" + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "200": { + "description": "Array of `OrderResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderResourcePublic" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/users/me": { + "get": { + "operationId": "users.getMeAction", + "summary": "Get Me", + "tags": [ + "Users" + ], + "responses": { + "200": { + "description": "`MeResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/MeResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/waitlist": { + "get": { + "operationId": "organizer.getWaitlistEntriesAction", + "summary": "Get Waitlist Entries", + "tags": [ + "Waitlist / Organizer" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 25 + } + }, + { + "name": "sort_by", + "in": "query", + "description": "Sortable fields are listed in the response `meta.allowed_sorts`.", + "schema": { + "type": "string" + } + }, + { + "name": "sort_direction", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "query", + "in": "query", + "description": "Search term used to filter the results.", + "schema": { + "type": "string" + } + }, + { + "name": "filter_fields", + "in": "query", + "description": "Filters in the form `filter_fields[field][operator]=value`. Filterable fields are listed in the response `meta.allowed_filter_fields`.", + "style": "deepObject", + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "explode": true + } + ], + "responses": { + "200": { + "description": "Array of `WaitlistEntryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WaitlistEntryResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/waitlist/stats": { + "get": { + "operationId": "organizer.getWaitlistStatsAction", + "summary": "Get Waitlist Stats", + "tags": [ + "Waitlist / Organizer" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "event_occurrence_id", + "in": "query", + "schema": { + "type": [ + "integer", + "null" + ] + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "waiting": { + "type": "integer" + }, + "offered": { + "type": "integer" + }, + "purchased": { + "type": "integer" + }, + "cancelled": { + "type": "integer" + }, + "expired": { + "type": "integer" + }, + "products": { + "type": "array", + "items": [] + } + }, + "required": [ + "total", + "waiting", + "offered", + "purchased", + "cancelled", + "expired", + "products" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/waitlist/offer-next": { + "post": { + "operationId": "organizer.offerWaitlistEntryAction", + "summary": "Offer Waitlist Entry", + "tags": [ + "Waitlist / Organizer" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OfferWaitlistEntryRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Array of `WaitlistEntryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WaitlistEntryResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/waitlist/{entryId}": { + "delete": { + "operationId": "organizer.cancelWaitlistEntryAction", + "summary": "Cancel Waitlist Entry", + "tags": [ + "Waitlist / Organizer" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "entryId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/events/{eventId}/waitlist": { + "post": { + "operationId": "public.createWaitlistEntryActionPublic", + "summary": "Create Waitlist Entry (public)", + "tags": [ + "Waitlist / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CreateWaitlistEntryRequest" + }, + { + "type": "object", + "properties": { + "locale": { + "type": "string", + "default": "en" + } + } + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "`WaitlistEntryResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WaitlistEntryResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/public/events/{eventId}/waitlist/{token}": { + "delete": { + "operationId": "public.cancelWaitlistEntryActionPublic", + "summary": "Cancel Waitlist Entry (public)", + "tags": [ + "Waitlist / Public" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [] + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + }, + "/events/{eventId}/webhooks": { + "post": { + "operationId": "webhooks.createWebhookAction", + "summary": "Create Webhook", + "tags": [ + "Webhooks" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertWebhookRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`WebhookResourceWithSecret`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WebhookResourceWithSecret" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "webhooks.getWebhooksAction", + "summary": "Get Webhooks", + "tags": [ + "Webhooks" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `WebhookResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/webhooks/{webhookId}": { + "put": { + "operationId": "webhooks.editWebhookAction", + "summary": "Edit Webhook", + "tags": [ + "Webhooks" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertWebhookRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`WebhookResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WebhookResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "get": { + "operationId": "webhooks.getWebhookAction", + "summary": "Get Webhook", + "tags": [ + "Webhooks" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "`WebhookResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WebhookResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "webhooks.deleteWebhookAction", + "summary": "Delete Webhook", + "tags": [ + "Webhooks" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/webhooks/{webhookId}/logs": { + "get": { + "operationId": "webhooks.getWebhookLogsAction", + "summary": "Get Webhook Logs", + "tags": [ + "Webhooks" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `WebhookLogResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookLogResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/widgets": { + "get": { + "operationId": "widgets.getOrganizerWidgetsAction", + "summary": "Get Organizer Widgets", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `WidgetResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WidgetResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "widgets.createOrganizerWidgetAction", + "summary": "Create Organizer Widget", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertOrganizerWidgetRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`WidgetResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WidgetResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/organizers/{organizerId}/widgets/{widgetId}": { + "put": { + "operationId": "widgets.updateOrganizerWidgetAction", + "summary": "Update Organizer Widget", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "widgetId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertOrganizerWidgetRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`WidgetResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WidgetResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "widgets.deleteOrganizerWidgetAction", + "summary": "Delete Organizer Widget", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "organizerId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "widgetId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/widgets": { + "get": { + "operationId": "widgets.getEventWidgetsAction", + "summary": "Get Event Widgets", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Array of `WidgetResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WidgetResource" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "widgets.createEventWidgetAction", + "summary": "Create Event Widget", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertEventWidgetRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`WidgetResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WidgetResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/events/{eventId}/widgets/{widgetId}": { + "put": { + "operationId": "widgets.updateEventWidgetAction", + "summary": "Update Event Widget", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "widgetId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertEventWidgetRequest" + } + } + } + }, + "responses": { + "200": { + "description": "`WidgetResource`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WidgetResource" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "422": { + "$ref": "#/components/responses/ValidationException" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "widgets.deleteEventWidgetAction", + "summary": "Delete Event Widget", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "eventId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "widgetId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "$ref": "#/components/responses/AuthenticationException" + }, + "403": { + "description": "The authenticated user is not allowed to perform this action.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "404": { + "description": "The requested resource was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/public/widgets/{widgetShortId}": { + "get": { + "operationId": "widgets.getWidgetPublicAction", + "summary": "Get Widget (public)", + "tags": [ + "Widgets" + ], + "parameters": [ + { + "name": "widgetShortId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`WidgetResourcePublic`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/WidgetResourcePublic" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "404": { + "description": "" + }, + "429": { + "description": "Too many requests. Retry once the rate limit window resets.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + }, + "security": [] + } + } + }, + "components": { + "securitySchemes": { + "http": { + "type": "http", + "description": "An API key (`hie_…`) or a session token from `POST /auth/login`.", + "scheme": "bearer" + } + }, + "schemas": { + "AccountDeletionRequestResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "initiated_by": { + "type": "string" + }, + "expected_outcome": { + "type": [ + "string", + "null" + ] + }, + "scheduled_deletion_at": { + "type": "string" + }, + "cancelled_at": { + "type": [ + "string", + "null" + ] + }, + "requested_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "status", + "initiated_by", + "expected_outcome", + "scheduled_deletion_at", + "cancelled_at", + "requested_at" + ], + "title": "AccountDeletionRequestResource" + }, + "AccountResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "currency_code": { + "type": "string" + }, + "timezone": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "require_two_factor_authentication": { + "type": "boolean" + }, + "is_account_email_confirmed": { + "type": "boolean" + }, + "is_saas_mode_enabled": { + "type": "string" + }, + "requires_manual_verification": { + "type": "string" + }, + "deletion_request": { + "anyOf": [ + { + "$ref": "#/components/schemas/AccountDeletionRequestResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "currency_code", + "timezone", + "updated_at", + "require_two_factor_authentication", + "is_account_email_confirmed", + "is_saas_mode_enabled", + "requires_manual_verification", + "deletion_request" + ], + "title": "AccountResource" + }, + "AffiliateResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "total_sales": { + "type": "integer" + }, + "total_sales_gross": { + "type": "number" + }, + "status": { + "type": "string" + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "event_id", + "account_id", + "name", + "code", + "email", + "total_sales", + "status", + "created_at", + "updated_at" + ], + "title": "AffiliateResource" + }, + "AttendeeCheckInPublicResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "short_id": { + "type": "string" + }, + "check_in_list_id": { + "type": "integer" + }, + "attendee_id": { + "type": "integer" + }, + "checked_in_at": { + "type": [ + "string", + "null" + ] + }, + "order_id": { + "type": [ + "integer", + "null" + ] + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "id", + "short_id", + "check_in_list_id", + "attendee_id", + "checked_in_at", + "order_id", + "event_occurrence_id" + ], + "title": "AttendeeCheckInPublicResource" + }, + "AttendeeCheckInResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "attendee_id": { + "type": "integer" + }, + "check_in_list_id": { + "type": "integer" + }, + "product_id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "short_id": { + "type": "string" + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "check_in_list": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "expires_at": { + "type": [ + "string", + "null" + ] + }, + "activates_at": { + "type": [ + "string", + "null" + ] + }, + "short_id": { + "type": "string" + }, + "is_system_default": { + "type": "boolean" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "total_attendees": { + "type": [ + "integer", + "null" + ] + }, + "checked_in_attendees": { + "type": [ + "integer", + "null" + ] + }, + "public_show_attendee_notes": { + "type": "boolean" + }, + "public_show_question_answers": { + "type": "boolean" + }, + "public_show_order_details": { + "type": "boolean" + }, + "is_expired": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResource" + }, + { + "type": "null" + } + ] + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductResource" + } + } + }, + "required": [ + "id", + "name", + "description", + "expires_at", + "activates_at", + "short_id", + "is_system_default", + "event_occurrence_id", + "total_attendees", + "checked_in_attendees", + "public_show_attendee_notes", + "public_show_question_answers", + "public_show_order_details" + ] + }, + "attendee": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "order_id": { + "type": "integer" + }, + "product_id": { + "type": "integer" + }, + "product_price_id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "AWAITING_PAYMENT", + "CANCELLED" + ] + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "public_id": { + "type": "string" + }, + "short_id": { + "type": "string" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "seat_uid": { + "type": [ + "string", + "null" + ] + }, + "seat_label": { + "type": [ + "string", + "null" + ] + }, + "locale": { + "type": "string" + }, + "notes": { + "type": [ + "string", + "null" + ] + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResource" + }, + { + "type": "null" + } + ] + }, + "product": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProductResource" + }, + { + "type": "null" + } + ] + }, + "check_ins": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/AttendeeCheckInResource" + } + }, + "order": { + "anyOf": [ + { + "$ref": "#/components/schemas/OrderResource" + }, + { + "type": "null" + } + ] + }, + "question_answers": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/QuestionAnswerViewResource" + } + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "order_id", + "product_id", + "product_price_id", + "event_id", + "email", + "status", + "first_name", + "last_name", + "public_id", + "short_id", + "event_occurrence_id", + "seat_uid", + "seat_label", + "locale", + "notes", + "created_at", + "updated_at" + ] + } + }, + "required": [ + "id", + "attendee_id", + "check_in_list_id", + "product_id", + "event_id", + "event_occurrence_id", + "short_id", + "created_at" + ], + "title": "AttendeeCheckInResource" + }, + "AttendeeDetailPublicResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "public_id": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "seat_label": { + "type": [ + "string", + "null" + ] + }, + "product_id": { + "type": "integer" + }, + "product_title": { + "type": [ + "string", + "null" + ] + }, + "event_occurrence": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "capacity": { + "type": [ + "integer", + "null" + ] + }, + "available_capacity": { + "type": [ + "integer", + "null" + ] + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "is_past": { + "type": "boolean" + }, + "is_future": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "event_location": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventLocationResourcePublic" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "event_id", + "start_date", + "end_date", + "status", + "label", + "is_past", + "is_future", + "is_active" + ] + }, + "check_ins": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "short_id": { + "type": "string" + }, + "check_in_list_id": { + "type": "integer" + }, + "attendee_id": { + "type": "integer" + }, + "checked_in_at": { + "type": [ + "string", + "null" + ] + }, + "order_id": { + "type": [ + "integer", + "null" + ] + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "id", + "short_id", + "check_in_list_id", + "attendee_id", + "checked_in_at", + "order_id", + "event_occurrence_id" + ] + } + }, + "visibility": { + "type": "object", + "properties": { + "notes": { + "type": "boolean" + }, + "question_answers": { + "type": "boolean" + }, + "order_details": { + "type": "boolean" + } + }, + "required": [ + "notes", + "question_answers", + "order_details" + ] + }, + "notes": { + "type": [ + "string", + "null" + ] + }, + "question_answers": { + "type": "array", + "items": [] + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "order": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "public_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "first_name": { + "type": [ + "string", + "null" + ] + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "public_id", + "status", + "first_name", + "last_name", + "email", + "created_at" + ] + } + }, + "required": [ + "id", + "public_id", + "first_name", + "last_name", + "status", + "seat_label", + "product_id", + "product_title", + "event_occurrence", + "check_ins", + "visibility", + "notes", + "question_answers", + "email", + "order" + ], + "title": "AttendeeDetailPublicResource" + }, + "AttendeeResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "order_id": { + "type": "integer" + }, + "product_id": { + "type": "integer" + }, + "product_price_id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "AWAITING_PAYMENT", + "CANCELLED" + ] + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "public_id": { + "type": "string" + }, + "short_id": { + "type": "string" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "seat_uid": { + "type": [ + "string", + "null" + ] + }, + "seat_label": { + "type": [ + "string", + "null" + ] + }, + "locale": { + "type": "string" + }, + "notes": { + "type": [ + "string", + "null" + ] + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResource" + }, + { + "type": "null" + } + ] + }, + "product": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProductResource" + }, + { + "type": "null" + } + ] + }, + "check_ins": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/AttendeeCheckInResource" + } + }, + "order": { + "anyOf": [ + { + "$ref": "#/components/schemas/OrderResource" + }, + { + "type": "null" + } + ] + }, + "question_answers": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/QuestionAnswerViewResource" + } + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "order_id", + "product_id", + "product_price_id", + "event_id", + "email", + "status", + "first_name", + "last_name", + "public_id", + "short_id", + "event_occurrence_id", + "seat_uid", + "seat_label", + "locale", + "notes", + "created_at", + "updated_at" + ], + "title": "AttendeeResource" + }, + "AttendeeResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "AWAITING_PAYMENT", + "CANCELLED" + ] + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "public_id": { + "type": "string" + }, + "short_id": { + "type": "string" + }, + "seat_uid": { + "type": [ + "string", + "null" + ] + }, + "seat_label": { + "type": [ + "string", + "null" + ] + }, + "product_id": { + "type": "integer" + }, + "product_price_id": { + "type": "integer" + }, + "product": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProductMinimalResourcePublic" + }, + { + "type": "null" + } + ] + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResourcePublic" + }, + { + "type": "null" + } + ] + }, + "locale": { + "type": "string" + }, + "is_checked_in": { + "type": "boolean" + } + }, + "required": [ + "id", + "email", + "status", + "first_name", + "last_name", + "public_id", + "short_id", + "seat_uid", + "seat_label", + "product_id", + "product_price_id", + "event_occurrence_id", + "locale" + ], + "title": "AttendeeResourcePublic" + }, + "AttendeeWithCheckInPublicResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "public_id": { + "type": "string" + }, + "seat_label": { + "type": [ + "string", + "null" + ] + }, + "product_id": { + "type": "integer" + }, + "product_price_id": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "locale": { + "type": "string" + }, + "order_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "event_occurrence": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "capacity": { + "type": [ + "integer", + "null" + ] + }, + "available_capacity": { + "type": [ + "integer", + "null" + ] + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "is_past": { + "type": "boolean" + }, + "is_future": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "event_location": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventLocationResourcePublic" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "event_id", + "start_date", + "end_date", + "status", + "label", + "is_past", + "is_future", + "is_active" + ] + }, + "check_in": { + "anyOf": [ + { + "$ref": "#/components/schemas/AttendeeCheckInPublicResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "first_name", + "last_name", + "public_id", + "seat_label", + "product_id", + "product_price_id", + "status", + "locale", + "order_id", + "event_occurrence_id", + "event_occurrence" + ], + "title": "AttendeeWithCheckInPublicResource" + }, + "BoxOfficeResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "short_id": { + "type": "string" + }, + "event_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "check_in_list_id": { + "type": [ + "integer", + "null" + ] + }, + "has_pin": { + "type": "boolean" + }, + "allow_price_override": { + "type": "boolean" + }, + "allow_discounts": { + "type": "boolean" + }, + "collect_order_questions": { + "type": "boolean" + }, + "is_system_default": { + "type": "boolean" + }, + "activates_at": { + "type": [ + "string", + "null" + ] + }, + "expires_at": { + "type": [ + "string", + "null" + ] + }, + "sales_count": { + "type": "integer" + }, + "gross_sales": { + "type": "number" + }, + "is_expired": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "currency": { + "type": "string" + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResource" + }, + { + "type": "null" + } + ] + }, + "check_in_list": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "short_id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "short_id", + "name" + ] + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductResource" + } + } + }, + "required": [ + "id", + "name", + "description", + "short_id", + "event_id", + "event_occurrence_id", + "check_in_list_id", + "has_pin", + "allow_price_override", + "allow_discounts", + "collect_order_questions", + "is_system_default", + "activates_at", + "expires_at", + "sales_count" + ], + "title": "BoxOfficeResource" + }, + "BoxOfficeStatsResource": { + "type": "object", + "properties": { + "currency": { + "type": "string" + }, + "orders": { + "type": "integer" + }, + "gross": { + "type": "number" + }, + "refunded": { + "type": "number" + }, + "by_tender": { + "type": "array", + "items": [] + }, + "by_operator": { + "type": "array", + "items": [] + }, + "by_day": { + "type": "array", + "items": [] + } + }, + "required": [ + "currency", + "orders", + "gross", + "refunded", + "by_tender", + "by_operator", + "by_day" + ], + "title": "BoxOfficeStatsResource" + }, + "BoxOfficeWithPinResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "short_id": { + "type": "string" + }, + "event_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "check_in_list_id": { + "type": [ + "integer", + "null" + ] + }, + "has_pin": { + "type": "boolean" + }, + "allow_price_override": { + "type": "boolean" + }, + "allow_discounts": { + "type": "boolean" + }, + "collect_order_questions": { + "type": "boolean" + }, + "is_system_default": { + "type": "boolean" + }, + "activates_at": { + "type": [ + "string", + "null" + ] + }, + "expires_at": { + "type": [ + "string", + "null" + ] + }, + "sales_count": { + "type": "integer" + }, + "gross_sales": { + "type": "number" + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductResource" + } + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResource" + }, + { + "type": "null" + } + ] + }, + "check_in_list": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "short_id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "short_id", + "name" + ] + }, + "pin": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "description", + "short_id", + "event_id", + "event_occurrence_id", + "check_in_list_id", + "has_pin", + "allow_price_override", + "allow_discounts", + "collect_order_questions", + "is_system_default", + "activates_at", + "expires_at", + "sales_count", + "pin" + ], + "title": "BoxOfficeWithPinResource" + }, + "BulkUpdateOccurrencesRequest": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "update", + "cancel", + "delete" + ] + }, + "start_time_shift": { + "type": [ + "integer", + "null" + ], + "minimum": -525600, + "maximum": 525600 + }, + "end_time_shift": { + "type": [ + "integer", + "null" + ], + "minimum": -525600, + "maximum": 525600 + }, + "capacity": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "clear_capacity": { + "type": [ + "boolean", + "null" + ] + }, + "future_only": { + "type": [ + "boolean", + "null" + ] + }, + "skip_overridden": { + "type": [ + "boolean", + "null" + ] + }, + "refund_orders": { + "type": [ + "boolean", + "null" + ] + }, + "apply_to_all": { + "type": [ + "boolean", + "null" + ] + }, + "occurrence_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "label": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "clear_label": { + "type": [ + "boolean", + "null" + ] + }, + "duration_minutes": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 10080 + }, + "event_location": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "IN_PERSON", + "ONLINE" + ] + }, + "location_id": { + "type": [ + "integer", + "null" + ] + }, + "online_event_connection_details": { + "type": [ + "string", + "null" + ], + "maxLength": 10000 + } + } + }, + "clear_event_location": { + "type": "boolean" + } + }, + "required": [ + "action" + ], + "title": "BulkUpdateOccurrencesRequest" + }, + "CancelOccurrenceRequest": { + "type": "object", + "properties": { + "refund_orders": { + "type": [ + "boolean", + "null" + ] + } + }, + "title": "CancelOccurrenceRequest" + }, + "CapacityAssignmentResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "capacity": { + "type": [ + "integer", + "null" + ] + }, + "used_capacity": { + "type": "integer" + }, + "percentage_used": { + "type": "number" + }, + "applies_to": { + "type": "string" + }, + "status": { + "type": "string" + }, + "event_id": { + "type": "integer" + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "id", + "title" + ] + } + } + }, + "required": [ + "id", + "name", + "capacity", + "used_capacity", + "percentage_used", + "applies_to", + "status", + "event_id" + ], + "title": "CapacityAssignmentResource" + }, + "CheckInAttendeeRequest": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "check_in", + "check_out" + ] + } + }, + "required": [ + "action" + ], + "title": "CheckInAttendeeRequest" + }, + "CheckInListResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "expires_at": { + "type": [ + "string", + "null" + ] + }, + "activates_at": { + "type": [ + "string", + "null" + ] + }, + "short_id": { + "type": "string" + }, + "is_system_default": { + "type": "boolean" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "total_attendees": { + "type": [ + "integer", + "null" + ] + }, + "checked_in_attendees": { + "type": [ + "integer", + "null" + ] + }, + "public_show_attendee_notes": { + "type": "boolean" + }, + "public_show_question_answers": { + "type": "boolean" + }, + "public_show_order_details": { + "type": "boolean" + }, + "is_expired": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResource" + }, + { + "type": "null" + } + ] + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductResource" + } + } + }, + "required": [ + "id", + "name", + "description", + "expires_at", + "activates_at", + "short_id", + "is_system_default", + "event_occurrence_id", + "total_attendees", + "checked_in_attendees", + "public_show_attendee_notes", + "public_show_question_answers", + "public_show_order_details" + ], + "title": "CheckInListResource" + }, + "CheckInListResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "short_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "is_system_default": { + "type": "boolean" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "event_occurrence": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "capacity": { + "type": [ + "integer", + "null" + ] + }, + "available_capacity": { + "type": [ + "integer", + "null" + ] + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "is_past": { + "type": "boolean" + }, + "is_future": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "event_location": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventLocationResourcePublic" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "event_id", + "start_date", + "end_date", + "status", + "label", + "is_past", + "is_future", + "is_active" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "expires_at": { + "type": [ + "string", + "null" + ] + }, + "activates_at": { + "type": [ + "string", + "null" + ] + }, + "total_attendees": { + "type": [ + "integer", + "null" + ] + }, + "checked_in_attendees": { + "type": [ + "integer", + "null" + ] + }, + "public_show_attendee_notes": { + "type": "boolean" + }, + "public_show_question_answers": { + "type": "boolean" + }, + "public_show_order_details": { + "type": "boolean" + }, + "is_expired": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "event": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventResourcePublic" + }, + { + "type": "null" + } + ] + }, + "event_occurrences": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventOccurrenceResourcePublic" + } + }, + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 0, + "maxItems": 0, + "additionalItems": false + } + ] + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductMinimalResourcePublic" + } + } + }, + "required": [ + "id", + "short_id", + "name", + "is_system_default", + "event_occurrence_id", + "event_occurrence", + "description", + "expires_at", + "activates_at", + "total_attendees", + "checked_in_attendees", + "public_show_attendee_notes", + "public_show_question_answers", + "public_show_order_details" + ], + "title": "CheckInListResourcePublic" + }, + "CheckInListStatsPublicResource": { + "type": "object", + "properties": { + "total_attendees": { + "type": "integer" + }, + "checked_in_attendees": { + "type": "integer" + }, + "per_product": { + "type": "array", + "items": [] + }, + "recent_check_ins": { + "type": "array", + "items": [] + } + }, + "required": [ + "total_attendees", + "checked_in_attendees", + "per_product", + "recent_check_ins" + ], + "title": "CheckInListStatsPublicResource" + }, + "CompleteOrderRequest": { + "type": "object", + "properties": { + "order": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "maxLength": 40 + }, + "last_name": { + "type": "string", + "maxLength": 40 + }, + "email": { + "type": "string", + "format": "email" + }, + "email_confirmation": { + "type": "string", + "format": "email" + }, + "questions": { + "type": "array", + "items": { + "type": "string" + } + }, + "address": { + "type": "object", + "properties": { + "address_line_1": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "address_line_2": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "city": { + "type": [ + "string", + "null" + ], + "maxLength": 85 + }, + "state_or_region": { + "type": [ + "string", + "null" + ], + "maxLength": 85 + }, + "zip_or_postal_code": { + "type": [ + "string", + "null" + ], + "maxLength": 85 + }, + "country": { + "type": [ + "string", + "null" + ], + "maxLength": 2 + } + } + } + }, + "required": [ + "first_name", + "last_name", + "email", + "email_confirmation" + ] + }, + "products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "seat_uid": { + "type": [ + "string", + "null" + ], + "maxLength": 24 + } + } + } + } + }, + "required": [ + "order" + ], + "title": "CompleteOrderRequest" + }, + "CreateAttendeeCheckInPublicRequest": { + "type": "object", + "properties": { + "attendees": { + "type": "array", + "items": { + "type": "object", + "properties": { + "public_id": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "check-in", + "check-in-and-mark-order-as-paid" + ] + } + }, + "required": [ + "public_id", + "action" + ] + }, + "minItems": 1 + } + }, + "required": [ + "attendees" + ], + "title": "CreateAttendeeCheckInPublicRequest" + }, + "CreateAttendeeRequest": { + "type": "object", + "properties": { + "product_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "product_price_id": { + "type": [ + "integer", + "null" + ] + }, + "seat_uid": { + "type": [ + "string", + "null" + ], + "maxLength": 24 + }, + "email": { + "type": "string", + "format": "email" + }, + "first_name": { + "type": "string", + "maxLength": 40 + }, + "last_name": { + "type": "string", + "maxLength": 40 + }, + "amount_paid": { + "type": "number", + "maximum": 999999999999 + }, + "send_confirmation_email": { + "type": "boolean" + }, + "taxes_and_fees": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tax_or_fee_id": { + "type": "integer" + }, + "amount": { + "type": "number", + "maximum": 999999999999 + } + }, + "required": [ + "tax_or_fee_id", + "amount" + ] + } + }, + "locale": { + "type": "string", + "enum": [ + "en", + "de", + "fi", + "fr", + "it", + "nl", + "hu", + "es", + "pt", + "pt-br", + "zh-cn", + "se", + "zh-hk", + "vi", + "tr", + "pl", + "sk", + "el", + "ko", + "ja" + ] + }, + "override_capacity": { + "type": "boolean" + } + }, + "required": [ + "product_id", + "email", + "first_name", + "amount_paid", + "send_confirmation_email", + "locale" + ], + "title": "CreateAttendeeRequest" + }, + "CreateEventImageRequest": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + "description": "Maximum file size: 5120 kilobytes." + }, + "type": { + "type": "string", + "enum": [ + "EVENT_COVER", + "TICKET_LOGO", + "EVENT_GALLERY", + "EVENT_SPONSOR_LOGO" + ] + } + }, + "required": [ + "image" + ], + "title": "CreateEventImageRequest" + }, + "CreateEventRequest": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "description": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "maxLength": 50000 + }, + "start_date": { + "type": "string", + "format": "date-time" + }, + "end_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "type": { + "type": [ + "string", + "null" + ], + "enum": [ + "SINGLE", + "RECURRING", + null + ] + }, + "timezone": { + "type": "string" + }, + "organizer_id": { + "type": "integer" + }, + "currency": { + "type": "string", + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "AOA", + "ARS", + "AUD", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "GBP", + "GEL", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "STD", + "SVC", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VND", + "VUV", + "WST", + "XCD", + "XAF", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW" + ] + }, + "category": { + "type": [ + "string", + "null" + ], + "enum": [ + "SOCIAL", + "FAMILY", + "HOBBIES", + "FOOD_DRINK", + "WELLNESS", + "SPIRITUALITY", + "OUTDOORS", + "TOURS", + "CHARITY", + "MUSIC", + "ART", + "COMEDY", + "THEATER", + "FILM", + "DANCE", + "BUSINESS", + "TECH", + "EDUCATION", + "WORKSHOP", + "SPORTS", + "FESTIVAL", + "SEASONAL", + "NIGHTLIFE", + "OTHER", + null + ] + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "todo - Revisit the 50k character limit", + "minLength": 1, + "maxLength": 50 + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 1000 + }, + "is_public": { + "type": "boolean" + } + }, + "required": [ + "name", + "value", + "is_public" + ] + } + }, + "event_location": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "IN_PERSON", + "ONLINE" + ] + }, + "location_id": { + "type": [ + "integer", + "null" + ] + }, + "online_event_connection_details": { + "type": [ + "string", + "null" + ], + "maxLength": 10000 + } + } + } + }, + "required": [ + "title", + "start_date", + "organizer_id" + ], + "title": "CreateEventRequest" + }, + "CreateImageRequest": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + "description": "Maximum file size: 5120 kilobytes." + }, + "image_type": { + "type": "string", + "enum": [ + "GENERIC", + "EVENT_COVER", + "TICKET_LOGO", + "EVENT_GALLERY", + "EVENT_SPONSOR_LOGO", + "ORGANIZER_LOGO", + "ORGANIZER_COVER", + "ORGANIZER_GALLERY", + "ORGANIZER_SPONSOR_LOGO", + "PROFILE_IMAGE" + ] + }, + "entity_id": { + "type": "integer" + } + }, + "required": [ + "image" + ], + "title": "CreateImageRequest" + }, + "CreateLineupEntryRequest": { + "type": "object", + "properties": { + "billing_tier": { + "$ref": "#/components/schemas/LineupBillingTier" + }, + "role_label": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "is_hidden": { + "type": "boolean" + }, + "profile_id": { + "type": "integer" + } + }, + "required": [ + "billing_tier", + "profile_id" + ], + "title": "CreateLineupEntryRequest" + }, + "CreateOrderRequest": { + "type": "object", + "properties": { + "products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "product_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "quantities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "quantity": { + "type": "integer", + "minimum": 0 + }, + "price_id": { + "type": "integer" + }, + "price": { + "type": [ + "number", + "null" + ], + "minimum": 0 + }, + "seat_uids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "maxLength": 24 + }, + "maxItems": 100 + }, + "band_key": { + "type": [ + "string", + "null" + ], + "maxLength": 24 + } + }, + "required": [ + "quantity", + "price_id" + ] + }, + "minItems": 1 + } + }, + "required": [ + "product_id", + "quantities" + ] + }, + "minItems": 1 + }, + "promo_code": { + "type": [ + "string", + "null" + ] + }, + "affiliate_code": { + "type": [ + "string", + "null" + ] + }, + "widget_id": { + "type": [ + "string", + "null" + ], + "maxLength": 50 + } + }, + "required": [ + "products" + ], + "title": "CreateOrderRequest" + }, + "CreateTaxOrFeeRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "calculation_type": { + "type": "string", + "enum": [ + "PERCENTAGE", + "FIXED" + ] + }, + "type": { + "type": "string", + "enum": [ + "TAX", + "FEE" + ] + }, + "rate": { + "type": "number" + }, + "is_active": { + "type": "boolean" + }, + "is_default": { + "type": "boolean" + }, + "description": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "name", + "calculation_type", + "type", + "rate", + "is_active", + "is_default" + ], + "title": "CreateTaxOrFeeRequest" + }, + "CreateUpdateAffiliateRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "code": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "maxLength": 50 + }, + "email": { + "type": [ + "string", + "null" + ], + "format": "email", + "maxLength": 255 + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "ACTIVE", + "INACTIVE", + null + ] + } + }, + "required": [ + "name", + "code" + ], + "title": "CreateUpdateAffiliateRequest" + }, + "CreateUpdatePromoCodeRequest": { + "type": "object", + "properties": { + "code": { + "type": "string", + "minLength": 2, + "maxLength": 50 + }, + "applicable_product_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "discount": { + "type": "number", + "minimum": 0 + }, + "expiry_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "max_allowed_usages": { + "type": [ + "string", + "null" + ], + "maxLength": 9999999 + }, + "discount_type": { + "type": "string", + "enum": [ + "NONE", + "FIXED", + "PERCENTAGE" + ] + }, + "discount_applies_to": { + "type": "string", + "enum": [ + "EACH_PRODUCT", + "ORDER" + ] + } + }, + "required": [ + "code", + "discount_type" + ], + "title": "CreateUpdatePromoCodeRequest" + }, + "CreateWaitlistEntryRequest": { + "type": "object", + "properties": { + "product_price_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 255 + }, + "first_name": { + "type": "string", + "maxLength": 255 + }, + "last_name": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + } + }, + "required": [ + "product_price_id", + "email", + "first_name" + ], + "title": "CreateWaitlistEntryRequest" + }, + "DuplicateEventRequest": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "description": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "maxLength": 50000 + }, + "start_date": { + "type": "string", + "format": "date-time" + }, + "end_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "duplicate_products": { + "type": "boolean" + }, + "duplicate_questions": { + "type": "boolean" + }, + "duplicate_settings": { + "type": "boolean" + }, + "duplicate_promo_codes": { + "type": "boolean" + }, + "duplicate_capacity_assignments": { + "type": "boolean" + }, + "duplicate_check_in_lists": { + "type": "boolean" + }, + "duplicate_event_cover_image": { + "type": "boolean" + }, + "duplicate_webhooks": { + "type": "boolean" + }, + "duplicate_affiliates": { + "type": "boolean" + }, + "duplicate_lineup": { + "type": [ + "boolean", + "null" + ] + }, + "duplicate_ticket_logo": { + "type": "boolean" + }, + "duplicate_occurrences": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "title", + "start_date", + "duplicate_products", + "duplicate_questions", + "duplicate_settings", + "duplicate_promo_codes", + "duplicate_capacity_assignments", + "duplicate_check_in_lists", + "duplicate_event_cover_image", + "duplicate_webhooks", + "duplicate_affiliates", + "duplicate_ticket_logo" + ], + "title": "DuplicateEventRequest" + }, + "EditAttendeePublicRequest": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "maxLength": 255 + }, + "last_name": { + "type": "string", + "maxLength": 255 + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 255 + } + }, + "title": "EditAttendeePublicRequest" + }, + "EditAttendeeRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 100 + }, + "first_name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "last_name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "product_id": { + "type": "number" + }, + "product_price_id": { + "type": "number" + }, + "notes": { + "type": [ + "string", + "null" + ], + "maxLength": 2000 + } + }, + "required": [ + "email", + "first_name", + "last_name", + "product_id", + "product_price_id" + ], + "title": "EditAttendeeRequest" + }, + "EditOrderPublicRequest": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "maxLength": 255 + }, + "last_name": { + "type": "string", + "maxLength": 255 + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 255 + } + }, + "title": "EditOrderPublicRequest" + }, + "EditOrderRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 100 + }, + "first_name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "last_name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "notes": { + "type": [ + "string", + "null" + ], + "maxLength": 2000 + } + }, + "required": [ + "email", + "first_name", + "last_name" + ], + "title": "EditOrderRequest" + }, + "EditQuestionAnswerRequest": { + "type": "object", + "properties": { + "answer": { + "type": [ + "string", + "null" + ] + } + }, + "title": "EditQuestionAnswerRequest" + }, + "EmailTemplateResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "organizer_id": { + "type": [ + "integer", + "null" + ] + }, + "event_id": { + "type": [ + "integer", + "null" + ] + }, + "template_type": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "body": { + "type": "string" + }, + "cta": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "engine": { + "type": "string" + }, + "is_active": { + "type": "boolean" + } + }, + "required": [ + "id", + "account_id", + "organizer_id", + "event_id", + "template_type", + "subject", + "body", + "cta", + "engine", + "is_active" + ], + "title": "EmailTemplateResource" + }, + "EmailTemplateType": { + "type": "string", + "enum": [ + "order_confirmation", + "attendee_ticket", + "occurrence_cancellation" + ], + "title": "EmailTemplateType" + }, + "EventLineupResource": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LineupEntryResource" + } + }, + "spaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventSpaceResource" + } + }, + "schedule_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScheduleItemResource" + } + }, + "venues": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/LocationPublicResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "location" + ] + } + } + }, + "required": [ + "entries", + "spaces", + "schedule_items", + "venues" + ], + "title": "EventLineupResource" + }, + "EventLineupResourcePublic": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LineupEntryResourcePublic" + } + }, + "spaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventSpaceResourcePublic" + } + }, + "schedule_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScheduleItemResource" + } + } + }, + "required": [ + "entries", + "spaces", + "schedule_items" + ], + "title": "EventLineupResourcePublic" + }, + "EventLocationResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "location_id": { + "type": [ + "integer", + "null" + ] + }, + "online_event_connection_details": { + "type": [ + "string", + "null" + ] + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/LocationResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "type", + "location_id" + ], + "title": "EventLocationResource" + }, + "EventLocationResourcePublic": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "online_event_connection_details": { + "type": [ + "string", + "null" + ] + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/LocationPublicResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "type" + ], + "title": "EventLocationResourcePublic" + }, + "EventOccurrenceResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "short_id": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "capacity": { + "type": [ + "integer", + "null" + ] + }, + "used_capacity": { + "type": "integer" + }, + "available_capacity": { + "type": [ + "integer", + "null" + ] + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "show_available_capacity": { + "type": [ + "boolean", + "null" + ] + }, + "is_overridden": { + "type": "boolean" + }, + "is_past": { + "type": "boolean" + }, + "is_future": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "event_location": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventLocationResource" + }, + { + "type": "null" + } + ] + }, + "booking_limits": { + "type": "object", + "properties": { + "capacity": { + "type": [ + "integer", + "null" + ] + }, + "allocation_total": { + "type": [ + "integer", + "null" + ] + }, + "sellable": { + "type": [ + "integer", + "null" + ] + }, + "allocations": { + "type": "array", + "items": [] + } + }, + "required": [ + "capacity", + "allocation_total", + "sellable", + "allocations" + ] + }, + "statistics": { + "type": "object", + "properties": { + "orders_created": { + "type": "integer" + }, + "attendees_registered": { + "type": "integer" + }, + "products_sold": { + "type": "integer" + }, + "": { + "anyOf": [ + { + "type": "object", + "properties": { + "total_gross_sales": { + "type": "number" + }, + "total_tax": { + "type": "number" + }, + "total_fee": { + "type": "number" + }, + "total_refunded": { + "type": "number" + } + }, + "required": [ + "total_gross_sales", + "total_tax", + "total_fee", + "total_refunded" + ] + }, + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 0, + "maxItems": 0, + "additionalItems": false + } + ] + } + }, + "required": [ + "orders_created", + "attendees_registered", + "products_sold", + null + ] + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "event_id", + "short_id", + "start_date", + "end_date", + "status", + "capacity", + "used_capacity", + "available_capacity", + "label", + "show_available_capacity", + "is_overridden", + "is_past", + "is_future", + "is_active", + "created_at", + "updated_at" + ], + "title": "EventOccurrenceResource" + }, + "EventOccurrenceResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "capacity": { + "type": [ + "integer", + "null" + ] + }, + "available_capacity": { + "type": [ + "integer", + "null" + ] + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "is_past": { + "type": "boolean" + }, + "is_future": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "event_location": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventLocationResourcePublic" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "event_id", + "start_date", + "end_date", + "status", + "label", + "is_past", + "is_future", + "is_active" + ], + "title": "EventOccurrenceResourcePublic" + }, + "EventResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "category": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "start_date": { + "type": [ + "string", + "null" + ] + }, + "end_date": { + "type": [ + "string", + "null" + ] + }, + "next_occurrence_start_date": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "DRAFT", + "LIVE", + "ARCHIVED", + "PENDING_MANUAL_REVIEW", + null + ] + }, + "type": { + "type": "string", + "enum": [ + "SINGLE", + "RECURRING" + ] + }, + "has_seat_map": { + "type": "boolean" + }, + "recurrence_rule": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "lifecycle_status": { + "type": "string", + "enum": [ + "UPCOMING", + "ONGOING", + "ENDED" + ] + }, + "currency": { + "type": "string" + }, + "timezone": { + "type": [ + "string", + "null" + ] + }, + "slug": { + "type": "string" + }, + "organizer_id": { + "type": [ + "integer", + "null" + ] + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductResource" + } + }, + "product_categories": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductCategoryResource" + } + }, + "attributes": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "images": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ImageResource" + } + }, + "event_location": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventLocationResource" + }, + { + "type": "null" + } + ] + }, + "settings": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventSettingsResource" + }, + { + "type": "null" + } + ] + }, + "organizer": { + "anyOf": [ + { + "$ref": "#/components/schemas/OrganizerResource" + }, + { + "type": "null" + } + ] + }, + "statistics": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventStatisticsResource" + }, + { + "type": "null" + } + ] + }, + "occurrences": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/EventOccurrenceResource" + } + } + }, + "required": [ + "id", + "title", + "category", + "description", + "start_date", + "end_date", + "next_occurrence_start_date", + "status", + "type", + "has_seat_map", + "recurrence_rule", + "lifecycle_status", + "currency", + "timezone", + "slug", + "organizer_id" + ], + "title": "EventResource" + }, + "EventResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "category": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "description_preview": { + "type": "string" + }, + "start_date": { + "type": [ + "string", + "null" + ] + }, + "end_date": { + "type": [ + "string", + "null" + ] + }, + "next_occurrence_start_date": { + "type": [ + "string", + "null" + ] + }, + "upcoming_occurrences_sold_out": { + "type": "boolean" + }, + "has_seat_map": { + "type": "boolean" + }, + "products_sold_out": { + "type": [ + "boolean", + "null" + ] + }, + "last_occurrence_date": { + "type": [ + "string", + "null" + ] + }, + "occurrences_month": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "SINGLE", + "RECURRING" + ] + }, + "currency": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "DRAFT", + "LIVE", + "ARCHIVED", + "PENDING_MANUAL_REVIEW", + null + ] + }, + "lifecycle_status": { + "type": "string", + "enum": [ + "UPCOMING", + "ONGOING", + "ENDED" + ] + }, + "timezone": { + "type": [ + "string", + "null" + ] + }, + "event_location": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventLocationResourcePublic" + }, + { + "type": "null" + } + ] + }, + "product_categories": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductCategoryResourcePublic" + } + }, + "settings": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventSettingsResourcePublic" + }, + { + "type": "null" + } + ] + }, + "questions": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/QuestionResource" + } + }, + "attributes": { + "type": "array", + "items": [] + }, + "images": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ImageResource" + } + }, + "organizer": { + "anyOf": [ + { + "$ref": "#/components/schemas/OrganizerResourcePublic" + }, + { + "type": "null" + } + ] + }, + "occurrences": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventOccurrenceResourcePublic" + } + } + }, + "required": [ + "id", + "title", + "category", + "description", + "description_preview", + "start_date", + "end_date", + "next_occurrence_start_date", + "upcoming_occurrences_sold_out", + "has_seat_map", + "type", + "currency", + "slug", + "status", + "lifecycle_status", + "timezone" + ], + "title": "EventResourcePublic" + }, + "EventSeatMapResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "version": { + "type": "integer" + }, + "layout": { + "type": "object" + }, + "source_seat_map": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "is_update_available_from_source": { + "type": "boolean" + }, + "prevent_orphan_seats": { + "type": "boolean" + }, + "max_seats_per_order": { + "type": [ + "integer", + "null" + ] + }, + "allow_seat_change": { + "type": "boolean" + }, + "band_products": { + "type": "array", + "items": [] + } + }, + "required": [ + "id", + "event_id", + "version", + "layout", + "source_seat_map", + "is_update_available_from_source", + "prevent_orphan_seats", + "max_seats_per_order", + "allow_seat_change", + "band_products" + ], + "title": "EventSeatMapResource" + }, + "EventSeatMapResourcePublic": { + "type": "object", + "properties": { + "version": { + "type": "integer" + }, + "layout": { + "type": "object" + }, + "band_products": { + "type": "array", + "items": [] + }, + "prevent_orphan_seats": { + "type": "boolean" + }, + "max_seats_per_order": { + "type": [ + "integer", + "null" + ] + }, + "allow_seat_change": { + "type": "boolean" + } + }, + "required": [ + "version", + "layout", + "band_products", + "prevent_orphan_seats", + "max_seats_per_order", + "allow_seat_change" + ], + "title": "EventSeatMapResourcePublic" + }, + "EventSettingsResource": { + "type": "object", + "properties": { + "pre_checkout_message": { + "type": [ + "string", + "null" + ] + }, + "post_checkout_message": { + "type": [ + "string", + "null" + ] + }, + "product_page_message": { + "type": [ + "string", + "null" + ] + }, + "continue_button_text": { + "type": [ + "string", + "null" + ] + }, + "get_tickets_button_text": { + "type": [ + "string", + "null" + ] + }, + "required_attendee_details": { + "type": "boolean" + }, + "attendee_details_collection_method": { + "type": "string" + }, + "email_footer_message": { + "type": [ + "string", + "null" + ] + }, + "support_email": { + "type": [ + "string", + "null" + ] + }, + "order_timeout_in_minutes": { + "type": "integer" + }, + "homepage_body_background_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_background_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_primary_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_primary_text_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_secondary_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_secondary_text_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_background_type": { + "type": "string" + }, + "website_url": { + "type": [ + "string", + "null" + ] + }, + "maps_url": { + "type": [ + "string", + "null" + ] + }, + "seo_title": { + "type": [ + "string", + "null" + ] + }, + "seo_description": { + "type": [ + "string", + "null" + ] + }, + "seo_keywords": { + "type": [ + "string", + "null" + ] + }, + "allow_search_engine_indexing": { + "type": "boolean" + }, + "notify_organizer_of_new_orders": { + "type": "boolean" + }, + "price_display_mode": { + "type": "string" + }, + "ticket_design_settings": { + "description": "Ticket design settings", + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "payment_providers": { + "description": "Payment settings", + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "offline_payment_instructions": { + "type": [ + "string", + "null" + ] + }, + "allow_orders_awaiting_offline_payment_to_check_in": { + "type": "boolean" + }, + "enable_invoicing": { + "type": "boolean", + "description": "Invoice settings" + }, + "invoice_label": { + "type": [ + "string", + "null" + ] + }, + "invoice_prefix": { + "type": [ + "string", + "null" + ] + }, + "invoice_start_number": { + "type": "integer" + }, + "require_billing_address": { + "type": "boolean" + }, + "organization_name": { + "type": [ + "string", + "null" + ] + }, + "organization_address": { + "type": [ + "string", + "null" + ] + }, + "invoice_tax_details": { + "type": [ + "string", + "null" + ] + }, + "invoice_notes": { + "type": [ + "string", + "null" + ] + }, + "invoice_payment_terms_days": { + "type": [ + "integer", + "null" + ] + }, + "show_marketing_opt_in": { + "type": "boolean", + "description": "Marketing settings" + }, + "allow_copy_details_to_all_attendees": { + "type": "boolean", + "description": "Attendee detail copy control" + }, + "pass_platform_fee_to_buyer": { + "type": "boolean", + "description": "Platform fee settings" + }, + "homepage_theme_settings": { + "description": "Homepage theme settings", + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "homepage_sections": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "allow_attendee_self_edit": { + "type": "boolean", + "description": "Self-service settings" + }, + "show_available_occurrence_capacity": { + "type": "boolean", + "description": "Occurrence display" + }, + "hide_sold_out_occurrences": { + "type": "boolean" + }, + "waitlist_auto_process": { + "type": "boolean", + "description": "Waitlist settings" + }, + "waitlist_offer_timeout_minutes": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "pre_checkout_message", + "post_checkout_message", + "product_page_message", + "continue_button_text", + "get_tickets_button_text", + "required_attendee_details", + "attendee_details_collection_method", + "email_footer_message", + "support_email", + "order_timeout_in_minutes", + "homepage_body_background_color", + "homepage_background_color", + "homepage_primary_color", + "homepage_primary_text_color", + "homepage_secondary_color", + "homepage_secondary_text_color", + "homepage_background_type", + "website_url", + "maps_url", + "seo_title", + "seo_description", + "seo_keywords", + "allow_search_engine_indexing", + "notify_organizer_of_new_orders", + "price_display_mode", + "ticket_design_settings", + "payment_providers", + "offline_payment_instructions", + "allow_orders_awaiting_offline_payment_to_check_in", + "enable_invoicing", + "invoice_label", + "invoice_prefix", + "invoice_start_number", + "require_billing_address", + "organization_name", + "organization_address", + "invoice_tax_details", + "invoice_notes", + "invoice_payment_terms_days", + "show_marketing_opt_in", + "allow_copy_details_to_all_attendees", + "pass_platform_fee_to_buyer", + "homepage_theme_settings", + "homepage_sections", + "allow_attendee_self_edit", + "show_available_occurrence_capacity", + "hide_sold_out_occurrences", + "waitlist_auto_process", + "waitlist_offer_timeout_minutes" + ], + "title": "EventSettingsResource" + }, + "EventSettingsResourcePublic": { + "type": "object", + "properties": { + "pre_checkout_message": { + "type": [ + "string", + "null" + ] + }, + "post_checkout_message": { + "type": [ + "string", + "null" + ] + }, + "product_page_message": { + "type": [ + "string", + "null" + ] + }, + "continue_button_text": { + "type": [ + "string", + "null" + ] + }, + "get_tickets_button_text": { + "type": [ + "string", + "null" + ] + }, + "required_attendee_details": { + "type": "boolean" + }, + "attendee_details_collection_method": { + "type": "string" + }, + "email_footer_message": { + "type": [ + "string", + "null" + ] + }, + "support_email": { + "type": [ + "string", + "null" + ] + }, + "order_timeout_in_minutes": { + "type": "integer" + }, + "homepage_body_background_color": { + "type": [ + "string", + "null" + ], + "description": "Homepage settings" + }, + "homepage_background_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_primary_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_primary_text_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_secondary_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_secondary_text_color": { + "type": [ + "string", + "null" + ] + }, + "homepage_background_type": { + "type": "string" + }, + "website_url": { + "type": [ + "string", + "null" + ] + }, + "maps_url": { + "type": [ + "string", + "null" + ] + }, + "ticket_design_settings": { + "description": "Ticket design settings", + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "seo_title": { + "type": [ + "string", + "null" + ], + "description": "SEO settings" + }, + "seo_description": { + "type": [ + "string", + "null" + ] + }, + "seo_keywords": { + "type": [ + "string", + "null" + ] + }, + "allow_search_engine_indexing": { + "type": "boolean" + }, + "price_display_mode": { + "type": "string" + }, + "payment_providers": { + "description": "Payment settings", + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "offline_payment_instructions": { + "type": [ + "string", + "null" + ] + }, + "allow_orders_awaiting_offline_payment_to_check_in": { + "type": "boolean" + }, + "require_billing_address": { + "type": "boolean", + "description": "Invoice settings" + }, + "invoice_label": { + "type": [ + "string", + "null" + ] + }, + "show_marketing_opt_in": { + "type": "boolean", + "description": "Marketing settings" + }, + "allow_copy_details_to_all_attendees": { + "type": "boolean", + "description": "Attendee detail copy control" + }, + "pass_platform_fee_to_buyer": { + "type": "boolean", + "description": "Platform fee settings" + }, + "homepage_theme_settings": { + "description": "Homepage theme settings", + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "homepage_sections": { + "anyOf": [ + { + "type": "array", + "items": [] + }, + { + "type": "null" + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "allow_attendee_self_edit": { + "type": "boolean", + "description": "Self-service settings" + }, + "show_available_occurrence_capacity": { + "type": "boolean", + "description": "Occurrence display" + }, + "hide_sold_out_occurrences": { + "type": "boolean" + }, + "waitlist_auto_process": { + "type": "boolean", + "description": "Waitlist settings" + }, + "waitlist_offer_timeout_minutes": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "pre_checkout_message", + "product_page_message", + "continue_button_text", + "get_tickets_button_text", + "required_attendee_details", + "attendee_details_collection_method", + "email_footer_message", + "support_email", + "order_timeout_in_minutes", + "homepage_body_background_color", + "homepage_background_color", + "homepage_primary_color", + "homepage_primary_text_color", + "homepage_secondary_color", + "homepage_secondary_text_color", + "homepage_background_type", + "website_url", + "maps_url", + "ticket_design_settings", + "seo_title", + "seo_description", + "seo_keywords", + "allow_search_engine_indexing", + "price_display_mode", + "payment_providers", + "offline_payment_instructions", + "allow_orders_awaiting_offline_payment_to_check_in", + "require_billing_address", + "invoice_label", + "show_marketing_opt_in", + "allow_copy_details_to_all_attendees", + "pass_platform_fee_to_buyer", + "homepage_theme_settings", + "homepage_sections", + "allow_attendee_self_edit", + "show_available_occurrence_capacity", + "hide_sold_out_occurrences", + "waitlist_auto_process", + "waitlist_offer_timeout_minutes" + ], + "title": "EventSettingsResourcePublic" + }, + "EventSpaceResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "event_location_id": { + "type": [ + "integer", + "null" + ] + }, + "venue": { + "anyOf": [ + { + "$ref": "#/components/schemas/LocationPublicResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "event_location_id", + "venue" + ], + "title": "EventSpaceResource" + }, + "EventSpaceResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "venue": { + "anyOf": [ + { + "$ref": "#/components/schemas/LocationPublicResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "venue" + ], + "title": "EventSpaceResourcePublic" + }, + "EventStatisticsResource": { + "type": "object", + "properties": { + "unique_views": { + "type": "integer" + }, + "total_views": { + "type": "integer" + }, + "products_sold": { + "type": "integer" + }, + "attendees_registered": { + "type": "integer" + }, + "sales_total_gross": { + "type": "number" + }, + "total_tax": { + "type": "number" + }, + "sales_total_before_additions": { + "type": "number" + }, + "total_fee": { + "type": "number" + }, + "total_refunded": { + "type": "number" + } + }, + "required": [ + "unique_views", + "total_views", + "products_sold", + "attendees_registered" + ], + "title": "EventStatisticsResource" + }, + "ExportProductPurchasesRequest": { + "type": "object", + "properties": { + "product_id": { + "type": [ + "integer", + "null" + ] + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "statuses": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "SOLD", + "AWAITING_PAYMENT", + "CANCELLED" + ] + }, + "maxItems": 3 + }, + "refund_statuses": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "REFUND_PENDING", + "REFUND_FAILED", + "REFUNDED", + "PARTIALLY_REFUNDED" + ] + }, + "maxItems": 4 + }, + "query": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + } + }, + "title": "ExportProductPurchasesRequest" + }, + "GenerateOccurrencesRequest": { + "type": "object", + "properties": { + "recurrence_rule": { + "type": "object", + "properties": { + "frequency": { + "type": "string", + "enum": [ + "daily", + "weekly", + "monthly", + "yearly" + ] + }, + "interval": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "range": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "count", + "until" + ] + }, + "count": { + "type": "integer", + "minimum": 1, + "maximum": 1200 + }, + "until": { + "type": "string", + "format": "date-time" + }, + "start": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + "required": [ + "type" + ] + }, + "days_of_week": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday" + ] + } + }, + "times_of_day": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "label": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "duration_minutes": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 10080 + } + } + }, + "maxItems": 24 + }, + "duration_minutes": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 10080 + }, + "default_capacity": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "excluded_dates": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "format": "date-time" + }, + "maxItems": 1200 + }, + "excluded_occurrences": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "maxItems": 1200 + }, + "additional_dates": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date-time" + }, + "time": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "date" + ] + }, + "maxItems": 1200 + }, + "monthly_pattern": { + "type": [ + "string", + "null" + ], + "enum": [ + "by_day_of_month", + "by_day_of_week", + null + ] + }, + "days_of_month": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer", + "minimum": 1, + "maximum": 31 + } + }, + "day_of_week": { + "type": [ + "string", + "null" + ], + "enum": [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + null + ] + }, + "week_position": { + "type": [ + "integer", + "null" + ], + "enum": [ + "-1", + "1", + "2", + "3", + "4", + null + ] + }, + "month": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 12 + } + }, + "required": [ + "frequency", + "range" + ] + } + }, + "required": [ + "recurrence_rule" + ], + "title": "GenerateOccurrencesRequest" + }, + "GeoPlaceResource": { + "type": "object", + "properties": { + "provider": { + "type": "string" + }, + "provider_place_id": { + "type": "string" + }, + "address": { + "type": "object", + "additionalProperties": [] + }, + "latitude": { + "type": [ + "number", + "null" + ] + }, + "longitude": { + "type": [ + "number", + "null" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "provider", + "provider_place_id", + "address", + "latitude", + "longitude", + "display_name" + ], + "title": "GeoPlaceResource" + }, + "GeoSuggestionResource": { + "type": "object", + "properties": { + "provider_place_id": { + "type": "string" + }, + "primary_text": { + "type": "string" + }, + "secondary_text": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "provider_place_id", + "primary_text", + "secondary_text" + ], + "title": "GeoSuggestionResource" + }, + "ImageResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "size": { + "type": [ + "integer", + "null" + ] + }, + "file_name": { + "type": [ + "string", + "null" + ] + }, + "mime_type": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": [ + "string", + "null" + ] + }, + "width": { + "type": [ + "integer", + "null" + ] + }, + "height": { + "type": [ + "integer", + "null" + ] + }, + "avg_colour": { + "type": [ + "string", + "null" + ] + }, + "lqip_base64": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "url", + "path", + "size", + "file_name", + "mime_type", + "type", + "width", + "height", + "avg_colour", + "lqip_base64" + ], + "title": "ImageResource" + }, + "ImportLineupRequest": { + "type": "object", + "properties": { + "people": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "tagline": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "bio": { + "type": [ + "string", + "null" + ], + "maxLength": 20000 + }, + "links": { + "type": "array", + "items": { + "type": "string", + "format": "uri", + "maxLength": 500 + }, + "maxItems": 12 + }, + "billing_tier": { + "anyOf": [ + { + "$ref": "#/components/schemas/LineupBillingTier" + }, + { + "type": "null" + } + ] + }, + "role_label": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "is_hidden": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "name" + ] + }, + "maxItems": 500 + }, + "slots": { + "type": "array", + "items": { + "type": "object", + "properties": { + "starts_at": { + "type": "string", + "format": "date-time" + }, + "ends_at": { + "type": "string", + "format": "date-time" + }, + "stage": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "title": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 20000 + }, + "people": { + "type": "array", + "items": { + "type": "string", + "maxLength": 255 + }, + "maxItems": 50 + } + }, + "required": [ + "starts_at", + "ends_at" + ] + }, + "maxItems": 1000 + } + }, + "required": [ + "people", + "slots" + ], + "title": "ImportLineupRequest" + }, + "JsonResource": { + "type": "string", + "title": "JsonResource" + }, + "LineupBillingTier": { + "type": "string", + "enum": [ + "HEADLINER", + "FEATURED", + "STANDARD" + ], + "title": "LineupBillingTier" + }, + "LineupEntryResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "profile_id": { + "type": "integer" + }, + "billing_tier": { + "type": "string", + "enum": [ + "HEADLINER", + "FEATURED", + "STANDARD" + ] + }, + "role_label": { + "type": [ + "string", + "null" + ] + }, + "is_hidden": { + "type": "boolean" + }, + "sort_order": { + "type": "integer" + }, + "profile": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProfileResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "profile_id", + "billing_tier", + "role_label", + "is_hidden", + "sort_order", + "profile" + ], + "title": "LineupEntryResource" + }, + "LineupEntryResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "billing_tier": { + "type": "string", + "enum": [ + "HEADLINER", + "FEATURED", + "STANDARD" + ] + }, + "role_label": { + "type": [ + "string", + "null" + ] + }, + "profile": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProfileResourcePublic" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "billing_tier", + "role_label", + "profile" + ], + "title": "LineupEntryResourcePublic" + }, + "LocationPublicResource": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ] + }, + "structured_address": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "latitude": { + "type": [ + "number", + "null" + ] + }, + "longitude": { + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "name", + "structured_address", + "latitude", + "longitude" + ], + "title": "LocationPublicResource" + }, + "LocationResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "organizer_id": { + "type": "integer" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "structured_address": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "latitude": { + "type": [ + "number", + "null" + ] + }, + "longitude": { + "type": [ + "number", + "null" + ] + }, + "provider": { + "type": [ + "string", + "null" + ] + }, + "provider_place_id": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "organizer_id", + "name", + "structured_address", + "latitude", + "longitude", + "provider", + "provider_place_id", + "created_at", + "updated_at" + ], + "title": "LocationResource" + }, + "MeResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "timezone": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "full_name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "is_email_verified": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "two_factor_enabled": { + "type": "boolean" + }, + "has_pending_email_change": { + "type": "boolean" + }, + "has_password": { + "type": "boolean" + }, + "marketing_opted_in_at": { + "type": [ + "string", + "null" + ] + }, + "is_impersonating": { + "type": "boolean" + }, + "impersonator_id": { + "type": "string" + }, + "enforce_email_confirmation_during_registration": { + "type": "boolean" + }, + "role": { + "type": [ + "string", + "null" + ] + }, + "is_account_owner": { + "type": [ + "boolean", + "null" + ] + }, + "last_login_at": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "account_id": { + "type": [ + "integer", + "null" + ] + }, + "notify_new_orders": { + "type": [ + "boolean", + "null" + ] + }, + "sso_sign_in": { + "type": [ + "string", + "null" + ], + "enum": [ + "SSO", + "EXEMPT", + "PASSWORD", + null + ] + }, + "access": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "enum": [ + "ACCOUNT", + "RESTRICTED" + ] + }, + "organizer_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "event_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "permissions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EDIT", + "VIEW_FINANCIALS", + "ISSUE_REFUNDS", + "MANAGE_PAYMENTS", + "DELETE_EVENTS", + "MANAGE_TEAM", + "MANAGE_ACCOUNT" + ] + } + } + }, + "required": [ + "scope", + "organizer_ids", + "event_ids", + "permissions" + ] + }, + "pending_email": { + "type": [ + "string", + "null" + ] + }, + "two_factor_setup_required": { + "type": "string" + }, + "password_reset_allowed": { + "type": "boolean" + }, + "feature_flags": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "licence": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "GRACE", + "LAPSED", + "NONE", + "DEV" + ] + }, + "expires_at": { + "type": [ + "string", + "null" + ] + }, + "grace_ends_at": { + "type": [ + "string", + "null" + ] + }, + "invalid_reason": { + "type": "string" + }, + "features_in_use": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "seating", + "box_office", + "white_label" + ] + } + } + }, + "required": [ + "status", + "expires_at", + "grace_ends_at", + "invalid_reason", + "features_in_use" + ] + } + }, + "required": [ + "id", + "timezone", + "first_name", + "last_name", + "full_name", + "email", + "is_email_verified", + "locale", + "two_factor_enabled", + "two_factor_setup_required", + "password_reset_allowed", + "feature_flags", + "licence" + ], + "title": "MeResource" + }, + "MessageResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "subject": { + "type": "string" + }, + "message": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attendee_ids": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "order_id": { + "type": [ + "integer", + "null" + ] + }, + "product_ids": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "sent_at": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "scheduled_at": { + "type": [ + "string", + "null" + ] + }, + "message_preview": { + "type": "string" + }, + "sent_by_user": { + "anyOf": [ + { + "$ref": "#/components/schemas/UserResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "event_id", + "subject", + "message", + "type", + "attendee_ids", + "order_id", + "product_ids", + "sent_at", + "status", + "scheduled_at", + "message_preview" + ], + "title": "MessageResource" + }, + "MoveAttendeeSeatRequest": { + "type": "object", + "properties": { + "seat_uid": { + "type": "string", + "maxLength": 24 + } + }, + "required": [ + "seat_uid" + ], + "title": "MoveAttendeeSeatRequest" + }, + "OccupiedSeatResource": { + "type": "object", + "properties": { + "seat_uid": { + "type": "string" + }, + "seat_label": { + "type": "string" + }, + "is_zone": { + "type": "boolean" + }, + "band_key": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "HELD", + "SOLD", + "BLOCKED" + ] + }, + "block_reason": { + "type": [ + "string", + "null" + ] + }, + "attendee_public_id": { + "type": [ + "string", + "null" + ] + }, + "attendee_name": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "seat_uid", + "seat_label", + "is_zone", + "band_key", + "status", + "block_reason", + "attendee_public_id", + "attendee_name" + ], + "title": "OccupiedSeatResource" + }, + "OccurrenceProductAvailabilityResource": { + "type": "object", + "properties": { + "product_id": { + "type": "integer" + }, + "product_price_id": { + "type": "integer" + }, + "quantity_sold": { + "type": "integer" + }, + "quantity_available": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "product_id", + "product_price_id", + "quantity_sold", + "quantity_available" + ], + "title": "OccurrenceProductAvailabilityResource" + }, + "OfferWaitlistEntryRequest": { + "type": "object", + "properties": { + "product_price_id": { + "type": "integer" + }, + "entry_id": { + "type": "integer" + }, + "quantity": { + "type": "integer", + "minimum": 1, + "maximum": 50 + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + } + }, + "title": "OfferWaitlistEntryRequest" + }, + "OrderItemResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "order_id": { + "type": "integer" + }, + "total_before_additions": { + "type": "number" + }, + "price": { + "type": "number" + }, + "price_before_discount": { + "type": [ + "number", + "null" + ] + }, + "taxes_and_fees_rollup": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "quantity": { + "type": "integer" + }, + "product_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "band_key": { + "type": [ + "string", + "null" + ] + }, + "item_name": { + "type": [ + "string", + "null" + ] + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "order_id", + "quantity", + "product_id", + "event_occurrence_id", + "band_key", + "item_name" + ], + "title": "OrderItemResource" + }, + "OrderItemResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "order_id": { + "type": "integer" + }, + "total_before_additions": { + "type": "number" + }, + "total_before_discount": { + "type": "number" + }, + "price": { + "type": "number" + }, + "price_before_discount": { + "type": [ + "number", + "null" + ] + }, + "quantity": { + "type": "integer" + }, + "product_id": { + "type": "integer" + }, + "product_price_id": { + "type": "integer" + }, + "item_name": { + "type": [ + "string", + "null" + ] + }, + "total_service_fee": { + "type": [ + "number", + "null" + ] + }, + "total_tax": { + "type": "number" + }, + "total_gross": { + "type": [ + "number", + "null" + ] + }, + "taxes_and_fees_rollup": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResourcePublic" + }, + { + "type": "null" + } + ] + }, + "product": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProductResourcePublic" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "order_id", + "total_before_additions", + "total_before_discount", + "price", + "price_before_discount", + "quantity", + "product_id", + "product_price_id", + "item_name", + "total_service_fee", + "total_tax", + "total_gross", + "taxes_and_fees_rollup", + "event_occurrence_id" + ], + "title": "OrderItemResourcePublic" + }, + "OrderResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "short_id": { + "type": "string" + }, + "total_before_additions": { + "type": "number" + }, + "total_gross": { + "type": "number" + }, + "total_tax": { + "type": "number" + }, + "total_fee": { + "type": "number" + }, + "total_refunded": { + "type": "number" + }, + "taxes_and_fees_rollup": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "status": { + "type": "string", + "enum": [ + "RESERVED", + "CANCELLED", + "COMPLETED", + "AWAITING_OFFLINE_PAYMENT", + "ABANDONED" + ] + }, + "refund_status": { + "type": [ + "string", + "null" + ], + "enum": [ + "REFUND_PENDING", + "REFUND_FAILED", + "REFUNDED", + "PARTIALLY_REFUNDED", + null + ] + }, + "payment_status": { + "type": [ + "string", + "null" + ], + "enum": [ + "NO_PAYMENT_REQUIRED", + "AWAITING_PAYMENT", + "AWAITING_OFFLINE_PAYMENT", + "PAYMENT_FAILED", + "PAYMENT_RECEIVED", + null + ] + }, + "currency": { + "type": "string" + }, + "first_name": { + "type": [ + "string", + "null" + ] + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "public_id": { + "type": "string" + }, + "is_partially_refunded": { + "type": "boolean" + }, + "is_fully_refunded": { + "type": "boolean" + }, + "is_free_order": { + "type": "boolean" + }, + "is_manually_created": { + "type": "boolean" + }, + "box_office_id": { + "type": [ + "integer", + "null" + ] + }, + "box_office_operator_name": { + "type": [ + "string", + "null" + ] + }, + "box_office_tender": { + "type": [ + "string", + "null" + ], + "enum": [ + "CASH", + "CARD", + "COMP", + "OTHER", + "FREE", + null + ] + }, + "box_office_amount_tendered": { + "type": [ + "number", + "null" + ] + }, + "box_office_change_due": { + "type": [ + "number", + "null" + ] + }, + "box_office_reference": { + "type": [ + "string", + "null" + ] + }, + "address": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "notes": { + "type": [ + "string", + "null" + ] + }, + "payment_provider": { + "type": [ + "string", + "null" + ] + }, + "promo_code": { + "type": [ + "string", + "null" + ] + }, + "event_id": { + "type": "integer" + }, + "order_items": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/OrderItemResource" + } + }, + "attendees": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/AttendeeResource" + } + }, + "question_answers": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/QuestionAnswerViewResource" + } + }, + "latest_invoice": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "invoice_number": { + "type": "string" + }, + "order_id": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "required": [ + "id", + "invoice_number", + "order_id", + "status" + ] + } + }, + "required": [ + "id", + "status", + "refund_status", + "payment_status", + "currency", + "first_name", + "last_name", + "email", + "created_at", + "public_id", + "is_partially_refunded", + "is_fully_refunded", + "is_free_order", + "is_manually_created", + "box_office_id", + "address", + "notes", + "payment_provider", + "promo_code", + "event_id" + ], + "title": "OrderResource" + }, + "OrderResourcePublic": { + "type": "object", + "properties": { + "short_id": { + "type": "string" + }, + "total_before_additions": { + "type": "number" + }, + "total_tax": { + "type": "number" + }, + "total_gross": { + "type": "number" + }, + "total_fee": { + "type": "number" + }, + "status": { + "type": "string", + "enum": [ + "RESERVED", + "CANCELLED", + "COMPLETED", + "AWAITING_OFFLINE_PAYMENT", + "ABANDONED" + ] + }, + "refund_status": { + "type": [ + "string", + "null" + ], + "enum": [ + "REFUND_PENDING", + "REFUND_FAILED", + "REFUNDED", + "PARTIALLY_REFUNDED", + null + ] + }, + "payment_status": { + "type": [ + "string", + "null" + ], + "enum": [ + "NO_PAYMENT_REQUIRED", + "AWAITING_PAYMENT", + "AWAITING_OFFLINE_PAYMENT", + "PAYMENT_FAILED", + "PAYMENT_RECEIVED", + null + ] + }, + "currency": { + "type": "string" + }, + "reserved_until": { + "type": [ + "string", + "null" + ] + }, + "is_expired": { + "type": "boolean" + }, + "first_name": { + "type": [ + "string", + "null" + ] + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "public_id": { + "type": "string" + }, + "is_payment_required": { + "type": "boolean" + }, + "promo_code": { + "type": [ + "string", + "null" + ] + }, + "taxes_and_fees_rollup": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "event": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventResourcePublic" + }, + { + "type": "null" + } + ] + }, + "latest_invoice": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "invoice_number": { + "type": "string" + }, + "order_id": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "required": [ + "id", + "invoice_number", + "order_id", + "status" + ] + }, + "address": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "order_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderItemResourcePublic" + } + }, + "attendees": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AttendeeResourcePublic" + } + }, + "seats": { + "type": "array", + "items": { + "type": "object", + "properties": { + "seat_uid": { + "type": "string" + }, + "seat_label": { + "type": "string" + }, + "order_item_id": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "seat_uid", + "seat_label", + "order_item_id" + ] + } + }, + "session_identifier": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "box_office_operator_name": { + "type": [ + "string", + "null" + ] + }, + "box_office_tender": { + "type": [ + "string", + "null" + ], + "enum": [ + "CASH", + "CARD", + "COMP", + "OTHER", + "FREE", + null + ] + }, + "box_office_amount_tendered": { + "type": [ + "number", + "null" + ] + }, + "box_office_change_due": { + "type": [ + "number", + "null" + ] + }, + "box_office_reference": { + "type": [ + "string", + "null" + ] + }, + "box_office_card_error": { + "type": [ + "object", + "null" + ], + "properties": { + "code": { + "type": [ + "string", + "null" + ] + }, + "message": { + "type": [ + "string", + "null" + ] + }, + "charge_id": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "code", + "message", + "charge_id" + ] + } + }, + "required": [ + "short_id", + "total_before_additions", + "total_tax", + "total_gross", + "total_fee", + "status", + "refund_status", + "payment_status", + "currency", + "reserved_until", + "first_name", + "last_name", + "email", + "public_id", + "is_payment_required", + "promo_code", + "taxes_and_fees_rollup" + ], + "title": "OrderResourcePublic" + }, + "OrganizerConfigurationResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "is_system_default": { + "type": "boolean" + }, + "default_for_currency": { + "type": [ + "string", + "null" + ] + }, + "application_fees": { + "type": "object", + "properties": { + "percentage": { + "type": "number" + }, + "fixed": { + "type": "number" + }, + "currency": { + "type": "string" + } + }, + "required": [ + "percentage", + "fixed", + "currency" + ] + }, + "bypass_application_fees": { + "type": "boolean" + } + }, + "required": [ + "id", + "name", + "is_system_default", + "default_for_currency", + "application_fees", + "bypass_application_fees" + ], + "title": "OrganizerConfigurationResource" + }, + "OrganizerResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "phone": { + "type": [ + "string", + "null" + ] + }, + "website": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "timezone": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "status": { + "type": "string" + }, + "location_id": { + "type": [ + "integer", + "null" + ] + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/LocationResource" + }, + { + "type": "null" + } + ] + }, + "images": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ImageResource" + } + }, + "settings": { + "anyOf": [ + { + "$ref": "#/components/schemas/OrganizerSettingsPublicResource" + }, + { + "type": "null" + } + ] + }, + "stripe_connect_setup_complete": { + "type": "boolean" + }, + "stripe_account_id": { + "type": [ + "string", + "null" + ] + }, + "configuration": { + "anyOf": [ + { + "$ref": "#/components/schemas/OrganizerConfigurationResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "email", + "phone", + "website", + "description", + "timezone", + "currency", + "slug", + "status", + "location_id" + ], + "title": "OrganizerResource" + }, + "OrganizerResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "website": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "slug": { + "type": "string" + }, + "status": { + "type": "string" + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/LocationPublicResource" + }, + { + "type": "null" + } + ] + }, + "images": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ImageResource" + } + }, + "events": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/EventResourcePublic" + } + }, + "settings": { + "anyOf": [ + { + "$ref": "#/components/schemas/OrganizerSettingsPublicResource" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "website", + "description", + "slug", + "status" + ], + "title": "OrganizerResourcePublic" + }, + "OrganizerSettingsPublicResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "organizer_id": { + "type": "integer" + }, + "default_attendee_details_collection_method": { + "type": "string" + }, + "default_show_marketing_opt_in": { + "type": "boolean" + }, + "default_pass_platform_fee_to_buyer": { + "type": "boolean" + }, + "default_allow_attendee_self_edit": { + "type": "boolean" + }, + "social_media_handles": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "homepage_theme_settings": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "homepage_sections": { + "anyOf": [ + { + "type": "array", + "items": [] + }, + { + "type": "null" + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "homepage_visibility": { + "type": "string" + }, + "website_url": { + "type": [ + "string", + "null" + ] + }, + "seo_keywords": { + "type": [ + "string", + "null" + ] + }, + "seo_title": { + "type": [ + "string", + "null" + ] + }, + "seo_description": { + "type": [ + "string", + "null" + ] + }, + "allow_search_engine_indexing": { + "type": "boolean" + }, + "tracking_pixels": { + "type": "array", + "items": [] + }, + "first_day_of_week": { + "type": "integer" + } + }, + "required": [ + "id", + "organizer_id", + "default_attendee_details_collection_method", + "default_show_marketing_opt_in", + "default_pass_platform_fee_to_buyer", + "default_allow_attendee_self_edit", + "social_media_handles", + "homepage_theme_settings", + "homepage_sections", + "homepage_visibility", + "website_url", + "seo_keywords", + "seo_title", + "seo_description", + "allow_search_engine_indexing", + "tracking_pixels", + "first_day_of_week" + ], + "title": "OrganizerSettingsPublicResource" + }, + "OrganizerSettingsResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "organizer_id": { + "type": "integer" + }, + "default_attendee_details_collection_method": { + "type": "string" + }, + "default_show_marketing_opt_in": { + "type": "boolean" + }, + "default_pass_platform_fee_to_buyer": { + "type": "boolean" + }, + "default_allow_attendee_self_edit": { + "type": "boolean" + }, + "social_media_handles": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "homepage_theme_settings": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "homepage_sections": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "homepage_visibility": { + "type": "string" + }, + "homepage_password": { + "type": [ + "string", + "null" + ] + }, + "website_url": { + "type": [ + "string", + "null" + ] + }, + "seo_keywords": { + "type": [ + "string", + "null" + ] + }, + "seo_title": { + "type": [ + "string", + "null" + ] + }, + "seo_description": { + "type": [ + "string", + "null" + ] + }, + "allow_search_engine_indexing": { + "type": "boolean" + }, + "tracking_pixels": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "tracking_consent_acknowledged": { + "type": "boolean" + }, + "first_day_of_week": { + "type": "integer" + } + }, + "required": [ + "id", + "organizer_id", + "default_attendee_details_collection_method", + "default_show_marketing_opt_in", + "default_pass_platform_fee_to_buyer", + "default_allow_attendee_self_edit", + "social_media_handles", + "homepage_theme_settings", + "homepage_sections", + "homepage_visibility", + "homepage_password", + "website_url", + "seo_keywords", + "seo_title", + "seo_description", + "allow_search_engine_indexing", + "tracking_pixels", + "tracking_consent_acknowledged", + "first_day_of_week" + ], + "title": "OrganizerSettingsResource" + }, + "OrganizerStatsResponseDTO": { + "type": "object", + "properties": { + "total_products_sold": { + "type": "integer" + }, + "total_attendees_registered": { + "type": "integer" + }, + "total_orders": { + "type": "integer" + }, + "total_gross_sales": { + "type": "number" + }, + "total_fees": { + "type": "number" + }, + "total_tax": { + "type": "number" + }, + "total_views": { + "type": "number" + }, + "total_refunded": { + "type": "number" + }, + "currency_code": { + "type": "string" + }, + "daily_stats": { + "type": "object" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "all_organizers_currencies": { + "type": "array", + "items": [] + } + }, + "required": [ + "total_products_sold", + "total_attendees_registered", + "total_orders", + "total_gross_sales", + "total_fees", + "total_tax", + "total_views", + "total_refunded", + "currency_code", + "daily_stats", + "start_date", + "end_date", + "all_organizers_currencies" + ], + "title": "OrganizerStatsResponseDTO" + }, + "OrganizerVatSettingResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "organizer_id": { + "type": "integer" + }, + "vat_registered": { + "type": "boolean" + }, + "vat_number": { + "type": [ + "string", + "null" + ] + }, + "vat_validated": { + "type": "boolean" + }, + "vat_validation_status": { + "type": "string" + }, + "vat_validation_error": { + "type": [ + "string", + "null" + ] + }, + "vat_validation_attempts": { + "type": "integer" + }, + "vat_validation_date": { + "type": [ + "string", + "null" + ] + }, + "business_name": { + "type": [ + "string", + "null" + ] + }, + "business_address": { + "type": [ + "string", + "null" + ] + }, + "vat_country_code": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "organizer_id", + "vat_registered", + "vat_number", + "vat_validated", + "vat_validation_status", + "vat_validation_error", + "vat_validation_attempts", + "vat_validation_date", + "business_name", + "business_address", + "vat_country_code", + "created_at", + "updated_at" + ], + "title": "OrganizerVatSettingResource" + }, + "OutgoingMessageResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "message_id": { + "type": "integer" + }, + "recipient": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "SENT", + "FAILED" + ] + }, + "delivered_at": { + "type": [ + "string", + "null" + ] + }, + "bounced_at": { + "type": [ + "string", + "null" + ] + }, + "complained_at": { + "type": [ + "string", + "null" + ] + }, + "subject": { + "type": "string" + }, + "created_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "message_id", + "recipient", + "status", + "delivered_at", + "bounced_at", + "complained_at", + "subject", + "created_at" + ], + "title": "OutgoingMessageResource" + }, + "PaginatedReportDTO": { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": [] + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "last_page": { + "type": "integer" + } + }, + "required": [ + "total", + "page", + "per_page", + "last_page" + ] + } + }, + "required": [ + "data", + "pagination" + ], + "title": "PaginatedReportDTO" + }, + "PartialEditAttendeeRequest": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "first_name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "last_name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 100 + } + }, + "title": "PartialEditAttendeeRequest" + }, + "PartialUpdateOrganizerSettingsRequest": { + "type": "object", + "properties": { + "default_attendee_details_collection_method": { + "type": [ + "string", + "null" + ], + "description": "Event defaults", + "enum": [ + "PER_TICKET", + "PER_ORDER", + null + ] + }, + "default_show_marketing_opt_in": { + "type": [ + "boolean", + "null" + ] + }, + "default_pass_platform_fee_to_buyer": { + "type": [ + "boolean", + "null" + ] + }, + "default_allow_attendee_self_edit": { + "type": [ + "boolean", + "null" + ] + }, + "facebook_handle": { + "type": [ + "string", + "null" + ], + "description": "Social handles", + "maxLength": 255 + }, + "instagram_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "twitter_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "linkedin_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "discord_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "tiktok_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "bluesky_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "youtube_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "snapchat_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "twitch_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "reddit_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "pinterest_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "whatsapp_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "telegram_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "vk_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "weibo_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "wechat_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "flickr_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "tumblr_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "quora_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "vimeo_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "github_handle": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "website_url": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "homepage_visibility": { + "type": [ + "string", + "null" + ], + "description": "Homepage", + "enum": [ + "PUBLIC", + "PASSWORD_PROTECTED", + null + ] + }, + "homepage_theme_settings": { + "type": "object", + "description": "Homepage theme settings", + "properties": { + "accent": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "background": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "mode": { + "type": [ + "string", + "null" + ], + "enum": [ + "light", + "dark", + null + ] + }, + "background_type": { + "type": [ + "string", + "null" + ], + "enum": [ + "MIRROR_COVER_IMAGE", + "COLOR", + null + ] + }, + "font_family": { + "type": [ + "string", + "null" + ], + "enum": [ + "Outfit", + "Inter", + "Roboto", + "Open Sans", + "Poppins", + "Montserrat", + "Lato", + "Nunito", + "Raleway", + "DM Sans", + "Plus Jakarta Sans", + "Work Sans", + "Space Grotesk", + "Manrope", + "Oswald", + "Bebas Neue", + "Playfair Display", + "Merriweather", + "Lora", + null + ] + } + } + }, + "homepage_sections": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "maxLength": 40 + }, + "type": { + "type": "string", + "enum": [ + "EVENTS", + "ABOUT", + "CONTACT", + "PAST_EVENTS", + "TEXT", + "GALLERY", + "FAQ", + "VIDEO", + "SPONSORS" + ] + }, + "enabled": { + "type": "boolean" + }, + "heading": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "body": { + "type": [ + "string", + "null" + ], + "maxLength": 50000 + }, + "faqs": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "question": { + "type": "string", + "maxLength": 300 + }, + "answer": { + "type": [ + "string", + "null" + ], + "maxLength": 5000 + } + }, + "required": [ + "question" + ] + }, + "maxItems": 50 + }, + "video_url": { + "type": [ + "string", + "null" + ], + "format": "uri", + "maxLength": 500 + }, + "image_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer" + }, + "maxItems": 40 + }, + "sponsors": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "image_id": { + "type": "integer" + }, + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "url": { + "type": [ + "string", + "null" + ], + "format": "uri", + "maxLength": 500 + } + }, + "required": [ + "image_id" + ] + }, + "maxItems": 60 + } + }, + "required": [ + "id", + "type" + ] + }, + "maxItems": 40 + }, + "seo_keywords": { + "type": [ + "string", + "null" + ], + "description": "SEO", + "maxLength": 255 + }, + "seo_title": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "seo_description": { + "type": [ + "string", + "null" + ], + "maxLength": 1000 + }, + "allow_search_engine_indexing": { + "type": [ + "boolean", + "null" + ] + }, + "homepage_password": { + "type": [ + "string", + "null" + ], + "description": "Password", + "maxLength": 100 + }, + "tracking_pixels": { + "type": [ + "array", + "null" + ], + "description": "Tracking pixels", + "items": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "facebook_pixel", + "google_analytics_4", + "google_tag_manager", + "tiktok_pixel" + ] + }, + "pixel_id": { + "type": "string", + "pattern": "^[a-zA-Z0-9\\-_]+$", + "maxLength": 50 + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "provider", + "pixel_id", + "enabled" + ] + }, + "maxItems": 10 + }, + "tracking_consent_acknowledged": { + "type": [ + "boolean", + "null" + ] + }, + "first_day_of_week": { + "type": "integer", + "minimum": 0, + "maximum": 6 + } + }, + "title": "PartialUpdateOrganizerSettingsRequest" + }, + "PlatformFeePreviewResource": { + "type": "object", + "properties": { + "event_currency": { + "type": "string" + }, + "fee_currency": { + "type": [ + "string", + "null" + ] + }, + "fixed_fee_original": { + "type": "number" + }, + "fixed_fee_converted": { + "type": "number" + }, + "percentage_fee": { + "type": "number" + }, + "sample_price": { + "type": "number" + }, + "platform_fee": { + "type": "number" + }, + "total": { + "type": "number" + } + }, + "required": [ + "event_currency", + "fee_currency", + "fixed_fee_original", + "fixed_fee_converted", + "percentage_fee", + "sample_price", + "platform_fee", + "total" + ], + "title": "PlatformFeePreviewResource" + }, + "ProductCategoryResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "is_hidden": { + "type": "boolean" + }, + "order": { + "type": "integer" + }, + "no_products_message": { + "type": [ + "string", + "null" + ] + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductResource" + } + } + }, + "required": [ + "id", + "name", + "description", + "is_hidden", + "order", + "no_products_message" + ], + "title": "ProductCategoryResource" + }, + "ProductCategoryResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "is_hidden": { + "type": "boolean" + }, + "order": { + "type": "integer" + }, + "no_products_message": { + "type": [ + "string", + "null" + ] + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductResourcePublic" + } + } + }, + "required": [ + "id", + "name", + "description", + "is_hidden", + "order", + "no_products_message" + ], + "title": "ProductCategoryResourcePublic" + }, + "ProductMinimalResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "event_id": { + "type": "integer" + }, + "prices": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductPriceResourcePublic" + } + }, + "product_category_id": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "id", + "title", + "type", + "event_id", + "product_category_id" + ], + "title": "ProductMinimalResourcePublic" + }, + "ProductOccurrenceVisibilityResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": "integer" + }, + "product_id": { + "type": "integer" + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "event_occurrence_id", + "product_id", + "created_at" + ], + "title": "ProductOccurrenceVisibilityResource" + }, + "ProductPriceOccurrenceOverrideResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": "integer" + }, + "product_price_id": { + "type": "integer" + }, + "price": { + "type": [ + "number", + "null" + ] + }, + "quantity_available": { + "type": [ + "integer", + "null" + ] + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "event_occurrence_id", + "product_price_id", + "price", + "quantity_available", + "created_at", + "updated_at" + ], + "title": "ProductPriceOccurrenceOverrideResource" + }, + "ProductPriceResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "price": { + "type": "number" + }, + "sale_start_date": { + "type": [ + "string", + "null" + ] + }, + "sale_end_date": { + "type": [ + "string", + "null" + ] + }, + "is_before_sale_start_date": { + "type": "boolean" + }, + "is_after_sale_end_date": { + "type": "boolean" + }, + "is_available": { + "type": [ + "boolean", + "null" + ] + }, + "initial_quantity_available": { + "type": [ + "integer", + "null" + ] + }, + "quantity_sold": { + "type": "integer" + }, + "quantity_applies_to": { + "type": "string", + "enum": [ + "OCCURRENCE", + "EVENT" + ] + }, + "is_sold_out": { + "type": "boolean" + }, + "is_locked_behind_earlier_tier": { + "type": "boolean" + }, + "is_hidden": { + "type": [ + "boolean", + "null" + ] + }, + "off_sale_reason": { + "type": [ + "string", + "null" + ] + }, + "price_including_taxes_and_fees": { + "type": "number" + } + }, + "required": [ + "id", + "label", + "price", + "sale_start_date", + "sale_end_date", + "is_before_sale_start_date", + "is_after_sale_end_date", + "is_available", + "initial_quantity_available", + "quantity_sold", + "quantity_applies_to", + "is_sold_out", + "is_locked_behind_earlier_tier", + "is_hidden", + "off_sale_reason", + "price_including_taxes_and_fees" + ], + "title": "ProductPriceResource" + }, + "ProductPriceResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "price": { + "type": "number" + }, + "sale_start_date": { + "type": [ + "string", + "null" + ] + }, + "sale_end_date": { + "type": [ + "string", + "null" + ] + }, + "price_including_taxes_and_fees": { + "type": "number" + }, + "price_before_discount": { + "type": [ + "number", + "null" + ] + }, + "is_discounted": { + "type": "boolean" + }, + "tax_total": { + "type": [ + "number", + "null" + ] + }, + "fee_total": { + "type": [ + "number", + "null" + ] + }, + "is_before_sale_start_date": { + "type": "boolean" + }, + "is_after_sale_end_date": { + "type": "boolean" + }, + "is_available": { + "type": [ + "boolean", + "null" + ] + }, + "is_sold_out": { + "type": "boolean" + }, + "quantity_applies_to": { + "type": "string", + "enum": [ + "OCCURRENCE", + "EVENT" + ] + }, + "is_locked_behind_earlier_tier": { + "type": "boolean" + }, + "band_prices": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "number" + } + }, + "quantity_remaining": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "id", + "label", + "price", + "sale_start_date", + "sale_end_date", + "price_including_taxes_and_fees", + "price_before_discount", + "is_discounted", + "tax_total", + "fee_total", + "is_before_sale_start_date", + "is_after_sale_end_date", + "is_available", + "is_sold_out", + "quantity_applies_to", + "is_locked_behind_earlier_tier", + "band_prices" + ], + "title": "ProductPriceResourcePublic" + }, + "ProductPurchaseResource": { + "type": "object", + "properties": { + "order_id": { + "type": "integer" + }, + "order_public_id": { + "type": "string" + }, + "order_status": { + "type": "string", + "enum": [ + "COMPLETED", + "CANCELLED", + "AWAITING_OFFLINE_PAYMENT" + ] + }, + "refund_status": { + "type": [ + "string", + "null" + ], + "enum": [ + "REFUND_PENDING", + "REFUND_FAILED", + "REFUNDED", + "PARTIALLY_REFUNDED", + null + ] + }, + "order_created_at": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "first_name": { + "type": [ + "string", + "null" + ] + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "product_title": { + "type": "string" + }, + "product_price_id": { + "type": [ + "integer", + "null" + ] + }, + "price_label": { + "type": [ + "string", + "null" + ] + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "occurrence_start_date": { + "type": [ + "string", + "null" + ] + }, + "sold_quantity": { + "type": "integer" + }, + "awaiting_payment_quantity": { + "type": "integer" + }, + "cancelled_quantity": { + "type": "integer" + }, + "line_total": { + "type": [ + "number", + "null" + ] + }, + "status": { + "type": "string", + "enum": [ + "SOLD", + "AWAITING_PAYMENT", + "CANCELLED" + ] + } + }, + "required": [ + "order_id", + "order_public_id", + "order_status", + "refund_status", + "order_created_at", + "currency", + "first_name", + "last_name", + "email", + "product_title", + "product_price_id", + "price_label", + "event_occurrence_id", + "occurrence_start_date", + "sold_quantity", + "awaiting_payment_quantity", + "cancelled_quantity", + "line_total", + "status" + ], + "title": "ProductPurchaseResource" + }, + "ProductPurchaseSummaryResource": { + "type": "object", + "properties": { + "sold_quantity": { + "type": "integer" + }, + "awaiting_payment_quantity": { + "type": "integer" + }, + "cancelled_quantity": { + "type": "integer" + }, + "buyer_count": { + "type": "integer" + }, + "gross_sales": { + "type": "number" + }, + "refunded_order_count": { + "type": "integer" + } + }, + "required": [ + "sold_quantity", + "awaiting_payment_quantity", + "cancelled_quantity", + "buyer_count", + "gross_sales", + "refunded_order_count" + ], + "title": "ProductPurchaseSummaryResource" + }, + "ProductResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "PAID", + "FREE", + "DONATION", + "TIERED", + "REGISTRATION" + ] + }, + "product_type": { + "type": "string", + "enum": [ + "TICKET", + "GENERAL" + ] + }, + "order": { + "type": "integer" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "price": { + "type": [ + "number", + "null" + ] + }, + "max_per_order": { + "type": "integer" + }, + "min_per_order": { + "type": "integer" + }, + "quantity_sold": { + "type": "integer" + }, + "sale_start_date": { + "type": [ + "string", + "null" + ] + }, + "sale_end_date": { + "type": [ + "string", + "null" + ] + }, + "event_id": { + "type": "integer" + }, + "initial_quantity_available": { + "type": [ + "integer", + "null" + ] + }, + "hide_before_sale_start_date": { + "type": "boolean" + }, + "hide_after_sale_end_date": { + "type": "boolean" + }, + "start_collapsed": { + "type": "boolean" + }, + "show_quantity_remaining": { + "type": "boolean" + }, + "hide_when_sold_out": { + "type": "boolean" + }, + "sequential_tier_release_enabled": { + "type": "boolean" + }, + "is_hidden_without_promo_code": { + "type": "boolean" + }, + "is_hidden": { + "type": [ + "boolean", + "null" + ] + }, + "is_before_sale_start_date": { + "type": "boolean" + }, + "is_after_sale_end_date": { + "type": "boolean" + }, + "is_available": { + "type": "boolean" + }, + "is_sold_out": { + "type": "boolean" + }, + "taxes_and_fees": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/TaxAndFeeResource" + } + }, + "prices": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductPriceResource" + } + }, + "product_category_id": { + "type": [ + "integer", + "null" + ] + }, + "is_highlighted": { + "type": "boolean" + }, + "highlight_message": { + "type": [ + "string", + "null" + ] + }, + "waitlist_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "is_addon_only": { + "type": "boolean" + }, + "addon_product_ids": { + "type": [ + "array", + "null" + ], + "items": [] + }, + "addons": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "id", + "title" + ] + } + } + }, + "required": [ + "id", + "title", + "type", + "product_type", + "order", + "description", + "max_per_order", + "min_per_order", + "quantity_sold", + "sale_start_date", + "sale_end_date", + "event_id", + "initial_quantity_available", + "hide_before_sale_start_date", + "hide_after_sale_end_date", + "start_collapsed", + "show_quantity_remaining", + "hide_when_sold_out", + "sequential_tier_release_enabled", + "is_hidden_without_promo_code", + "is_hidden", + "is_before_sale_start_date", + "is_after_sale_end_date", + "is_available", + "product_category_id", + "is_highlighted", + "highlight_message", + "waitlist_enabled", + "is_addon_only" + ], + "title": "ProductResource" + }, + "ProductResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "PAID", + "FREE", + "DONATION", + "TIERED", + "REGISTRATION" + ] + }, + "product_type": { + "type": "string", + "enum": [ + "TICKET", + "GENERAL" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "max_per_order": { + "type": [ + "integer", + "null" + ] + }, + "min_per_order": { + "type": [ + "integer", + "null" + ] + }, + "sale_start_date": { + "type": [ + "string", + "null" + ] + }, + "sale_end_date": { + "type": [ + "string", + "null" + ] + }, + "event_id": { + "type": "integer" + }, + "is_before_sale_start_date": { + "type": "boolean" + }, + "is_after_sale_end_date": { + "type": "boolean" + }, + "start_collapsed": { + "type": "boolean" + }, + "quantity_available": { + "type": "integer" + }, + "price": { + "type": [ + "number", + "null" + ] + }, + "prices": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ProductPriceResourcePublic" + } + }, + "taxes": { + "type": [ + "array", + "null" + ], + "description": "todo: this should be taxes_and_fees", + "items": { + "$ref": "#/components/schemas/TaxAndFeeResource" + } + }, + "is_available": { + "type": "boolean" + }, + "is_sold_out": { + "type": "boolean" + }, + "product_category_id": { + "type": [ + "integer", + "null" + ] + }, + "is_highlighted": { + "type": "boolean" + }, + "highlight_message": { + "type": [ + "string", + "null" + ] + }, + "waitlist_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "is_addon_only": { + "type": "boolean" + }, + "addon_product_ids": { + "type": [ + "array", + "null" + ], + "items": [] + } + }, + "required": [ + "id", + "title", + "type", + "product_type", + "description", + "max_per_order", + "min_per_order", + "sale_start_date", + "sale_end_date", + "event_id", + "is_before_sale_start_date", + "is_after_sale_end_date", + "start_collapsed", + "product_category_id", + "is_highlighted", + "highlight_message", + "waitlist_enabled", + "is_addon_only" + ], + "title": "ProductResourcePublic" + }, + "ProfileResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "organizer_id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "tagline": { + "type": [ + "string", + "null" + ] + }, + "bio": { + "type": [ + "string", + "null" + ] + }, + "links": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + }, + { + "type": "array", + "items": [] + } + ] + }, + "images": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageResource" + } + }, + "appearances": { + "type": "array", + "items": { + "type": "object", + "properties": { + "event_id": { + "type": "integer" + }, + "event_title": { + "type": [ + "string", + "null" + ] + }, + "event_status": { + "type": "string", + "enum": [ + "DRAFT", + "LIVE", + "ARCHIVED", + "PENDING_MANUAL_REVIEW" + ] + }, + "billing_tier": { + "type": "string" + }, + "role_label": { + "type": [ + "string", + "null" + ] + }, + "is_hidden": { + "type": "boolean" + } + }, + "required": [ + "event_id", + "event_title", + "event_status", + "billing_tier", + "role_label", + "is_hidden" + ] + } + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "organizer_id", + "name", + "tagline", + "bio", + "links", + "images", + "created_at", + "updated_at" + ], + "title": "ProfileResource" + }, + "ProfileResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "tagline": { + "type": [ + "string", + "null" + ] + }, + "bio": { + "type": [ + "string", + "null" + ] + }, + "links": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + }, + { + "type": "array", + "items": [] + } + ] + }, + "images": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageResource" + } + } + }, + "required": [ + "id", + "name", + "tagline", + "bio", + "links", + "images" + ], + "title": "ProfileResourcePublic" + }, + "PromoCodeResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "code": { + "type": "string" + }, + "applicable_product_ids": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "discount": { + "type": "number" + }, + "discount_type": { + "type": [ + "string", + "null" + ] + }, + "discount_applies_to": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "expiry_date": { + "type": [ + "string", + "null" + ] + }, + "attendee_usage_count": { + "type": "integer" + }, + "order_usage_count": { + "type": "integer" + }, + "max_allowed_usages": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "id", + "code", + "applicable_product_ids", + "discount", + "discount_type", + "discount_applies_to", + "created_at", + "updated_at", + "expiry_date", + "attendee_usage_count", + "order_usage_count", + "max_allowed_usages" + ], + "title": "PromoCodeResource" + }, + "QuestionAnswerViewResource": { + "type": "object", + "properties": { + "product_id": { + "type": [ + "integer", + "null" + ] + }, + "product_title": { + "type": [ + "string", + "null" + ] + }, + "question_id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "question_required": { + "type": "boolean" + }, + "question_description": { + "type": [ + "string", + "null" + ] + }, + "answer": { + "anyOf": [ + { + "type": "array", + "items": [] + }, + { + "type": "string" + } + ] + }, + "text_answer": { + "type": "string" + }, + "order_id": { + "type": [ + "integer", + "null" + ] + }, + "belongs_to": { + "type": "string" + }, + "question_type": { + "type": "string" + }, + "event_id": { + "type": "integer" + }, + "question_answer_id": { + "type": "integer" + }, + "question_options": { + "type": [ + "array", + "null" + ], + "items": [] + }, + "attendee_id": { + "type": [ + "integer", + "null" + ] + }, + "first_name": { + "type": [ + "string", + "null" + ] + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "attendee_public_id": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "product_id", + "product_title", + "question_id", + "title", + "question_required", + "question_description", + "answer", + "text_answer", + "order_id", + "belongs_to", + "question_type", + "event_id", + "question_answer_id", + "question_options" + ], + "title": "QuestionAnswerViewResource" + }, + "QuestionResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "type": { + "type": [ + "string", + "null" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "options": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "required": { + "type": "boolean" + }, + "event_id": { + "type": "integer" + }, + "belongs_to": { + "type": "string" + }, + "is_hidden": { + "type": "boolean" + }, + "product_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "type", + "title", + "description", + "options", + "required", + "event_id", + "belongs_to", + "is_hidden" + ], + "title": "QuestionResource" + }, + "QuestionResourcePublic": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "type": { + "type": [ + "string", + "null" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "options": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": [] + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "required": { + "type": "boolean" + }, + "event_id": { + "type": "integer" + }, + "belongs_to": { + "type": "string" + }, + "order": { + "type": "integer" + }, + "product_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "type", + "title", + "description", + "options", + "required", + "event_id", + "belongs_to", + "order" + ], + "title": "QuestionResourcePublic" + }, + "RefundOrderRequest": { + "type": "object", + "properties": { + "amount": { + "type": "number" + }, + "notify_buyer": { + "type": "boolean" + }, + "cancel_order": { + "type": "boolean" + } + }, + "required": [ + "amount", + "notify_buyer", + "cancel_order" + ], + "title": "RefundOrderRequest" + }, + "ScheduleItemResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "starts_at": { + "type": "string" + }, + "ends_at": { + "type": "string" + }, + "event_space_id": { + "type": [ + "integer", + "null" + ] + }, + "lineup_entry_ids": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id", + "title", + "description", + "starts_at", + "ends_at", + "event_space_id", + "lineup_entry_ids" + ], + "title": "ScheduleItemResource" + }, + "SeatAvailabilityResourcePublic": { + "type": "object", + "properties": { + "version": { + "type": "integer" + }, + "unavailable_seat_uids": { + "type": "array", + "items": { + "type": "string" + } + }, + "zone_remaining": { + "type": "string" + }, + "band_free": { + "type": "string" + } + }, + "required": [ + "version", + "unavailable_seat_uids", + "zone_remaining", + "band_free" + ], + "title": "SeatAvailabilityResourcePublic" + }, + "SeatBlockRequest": { + "type": "object", + "properties": { + "all_upcoming_dates": { + "type": "boolean" + }, + "event_occurrence_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1, + "maxItems": 1200 + }, + "seat_uids": { + "type": "array", + "items": { + "type": "string", + "maxLength": 24 + }, + "minItems": 1, + "maxItems": 500 + }, + "reason": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + } + }, + "required": [ + "seat_uids" + ], + "title": "SeatBlockRequest" + }, + "SeatMapDiffResource": { + "type": "object", + "properties": { + "added_seat_count": { + "type": "integer" + }, + "removed_seat_labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "relabelled_seat_count": { + "type": "integer" + }, + "rebanded_seat_count": { + "type": "integer" + } + }, + "required": [ + "added_seat_count", + "removed_seat_labels", + "relabelled_seat_count", + "rebanded_seat_count" + ], + "title": "SeatMapDiffResource" + }, + "SeatMapPreviewResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "organizer_id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "version": { + "type": "integer" + }, + "seat_count": { + "type": "integer" + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "preview_layout": { + "type": "object" + } + }, + "required": [ + "id", + "organizer_id", + "name", + "version", + "seat_count", + "created_at", + "updated_at", + "preview_layout" + ], + "title": "SeatMapPreviewResource" + }, + "SeatMapResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "organizer_id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "version": { + "type": "integer" + }, + "seat_count": { + "type": "integer" + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "layout": { + "type": "object" + } + }, + "required": [ + "id", + "organizer_id", + "name", + "version", + "seat_count", + "created_at", + "updated_at", + "layout" + ], + "title": "SeatMapResource" + }, + "SendMessageRequest": { + "type": "object", + "properties": { + "subject": { + "type": "string", + "maxLength": 100 + }, + "message": { + "type": "string", + "maxLength": 8000 + }, + "message_type": { + "type": "string", + "enum": [ + "ORDER_OWNER", + "TICKET_HOLDERS", + "INDIVIDUAL_ATTENDEES", + "ALL_ATTENDEES", + "ORDER_OWNERS_WITH_PRODUCT" + ] + }, + "is_test": { + "type": "boolean" + }, + "send_copy_to_current_user": { + "type": "boolean" + }, + "attendee_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "maxItems": 50 + }, + "product_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "order_id": { + "type": "integer" + }, + "order_statuses": { + "type": "object" + }, + "scheduled_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "event_occurrence_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer" + }, + "maxItems": 500 + } + }, + "required": [ + "subject", + "message", + "message_type" + ], + "title": "SendMessageRequest" + }, + "SendTicketLookupEmailRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + } + }, + "required": [ + "email" + ], + "title": "SendTicketLookupEmailRequest" + }, + "SortLineupEntriesRequest": { + "type": "object", + "properties": { + "sorted_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1, + "maxItems": 1000 + } + }, + "required": [ + "sorted_ids" + ], + "title": "SortLineupEntriesRequest" + }, + "SortProductsRequest": { + "type": "object", + "properties": { + "sorted_categories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "product_category_id": { + "type": "integer" + }, + "sorted_products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "order": { + "type": "integer" + } + }, + "required": [ + "id" + ] + } + } + }, + "required": [ + "product_category_id" + ] + }, + "minItems": 1 + } + }, + "required": [ + "sorted_categories" + ], + "title": "SortProductsRequest" + }, + "SortProfileImagesRequest": { + "type": "object", + "properties": { + "sorted_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1, + "maxItems": 50 + } + }, + "required": [ + "sorted_ids" + ], + "title": "SortProfileImagesRequest" + }, + "SortQuestionsRequest": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "order": { + "type": "integer" + } + }, + "required": [ + "id", + "order" + ] + }, + "title": "SortQuestionsRequest" + }, + "TaxAndFeeResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "calculation_type": { + "type": "string" + }, + "rate": { + "type": "number" + }, + "is_active": { + "type": [ + "boolean", + "null" + ] + }, + "is_default": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "name", + "description", + "calculation_type", + "rate", + "is_active", + "is_default", + "type" + ], + "title": "TaxAndFeeResource" + }, + "UpdateAffiliateRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "email": { + "type": [ + "string", + "null" + ], + "format": "email", + "maxLength": 255 + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "ACTIVE", + "INACTIVE", + null + ] + } + }, + "title": "UpdateAffiliateRequest" + }, + "UpdateEventLocationRequest": { + "type": "object", + "properties": { + "event_location": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "IN_PERSON", + "ONLINE" + ] + }, + "location_id": { + "type": [ + "integer", + "null" + ] + }, + "online_event_connection_details": { + "type": [ + "string", + "null" + ], + "maxLength": 10000 + } + } + }, + "clear_event_location": { + "type": "boolean" + } + }, + "title": "UpdateEventLocationRequest" + }, + "UpdateEventRequest": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "description": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "maxLength": 50000 + }, + "start_date": { + "type": "string", + "format": "date-time" + }, + "end_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "type": { + "type": [ + "string", + "null" + ], + "enum": [ + "SINGLE", + "RECURRING", + null + ] + }, + "timezone": { + "type": "string" + }, + "currency": { + "type": "string", + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "AOA", + "ARS", + "AUD", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "GBP", + "GEL", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "STD", + "SVC", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VND", + "VUV", + "WST", + "XCD", + "XAF", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW" + ] + }, + "category": { + "type": [ + "string", + "null" + ], + "enum": [ + "SOCIAL", + "FAMILY", + "HOBBIES", + "FOOD_DRINK", + "WELLNESS", + "SPIRITUALITY", + "OUTDOORS", + "TOURS", + "CHARITY", + "MUSIC", + "ART", + "COMEDY", + "THEATER", + "FILM", + "DANCE", + "BUSINESS", + "TECH", + "EDUCATION", + "WORKSHOP", + "SPORTS", + "FESTIVAL", + "SEASONAL", + "NIGHTLIFE", + "OTHER", + null + ] + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "todo - Revisit the 50k character limit", + "minLength": 1, + "maxLength": 50 + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 1000 + }, + "is_public": { + "type": "boolean" + } + }, + "required": [ + "name", + "value", + "is_public" + ] + } + } + }, + "required": [ + "title", + "start_date" + ], + "title": "UpdateEventRequest" + }, + "UpdateEventSeatMapBandProductsRequest": { + "type": "object", + "properties": { + "band_products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "band_key": { + "type": "string", + "maxLength": 24 + }, + "products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "product_id": { + "type": "integer" + }, + "price_adjustment": { + "type": "integer", + "minimum": -10000000, + "maximum": 10000000 + } + }, + "required": [ + "product_id" + ] + } + } + }, + "required": [ + "band_key", + "products" + ] + }, + "maxItems": 20 + } + }, + "required": [ + "band_products" + ], + "title": "UpdateEventSeatMapBandProductsRequest" + }, + "UpdateEventSeatMapLayoutRequest": { + "type": "object", + "properties": { + "version": { + "type": [ + "integer", + "null" + ] + }, + "confirm_relabel": { + "type": [ + "boolean", + "null" + ] + }, + "layout": { + "type": "object", + "properties": { + "schema": { + "type": "integer" + }, + "bands": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "areas": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "required": [ + "schema", + "bands", + "areas" + ] + } + }, + "required": [ + "layout" + ], + "title": "UpdateEventSeatMapLayoutRequest" + }, + "UpdateEventSettingsRequest": { + "type": "object", + "properties": { + "post_checkout_message": { + "type": [ + "string", + "null" + ] + }, + "pre_checkout_message": { + "type": [ + "string", + "null" + ] + }, + "email_footer_message": { + "type": [ + "string", + "null" + ] + }, + "continue_button_text": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "get_tickets_button_text": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "support_email": { + "type": [ + "string", + "null" + ], + "format": "email" + }, + "require_attendee_details": { + "type": "boolean" + }, + "attendee_details_collection_method": { + "type": "string", + "enum": [ + "PER_TICKET", + "PER_ORDER" + ] + }, + "order_timeout_in_minutes": { + "type": "number", + "minimum": 1, + "maximum": 120 + }, + "homepage_background_color": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "homepage_primary_color": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "homepage_primary_text_color": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "homepage_secondary_color": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "homepage_secondary_text_color": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "homepage_body_background_color": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "homepage_background_type": { + "type": [ + "string", + "null" + ], + "enum": [ + "MIRROR_COVER_IMAGE", + "COLOR", + null + ] + }, + "website_url": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "maps_url": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "seo_title": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "seo_description": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "seo_keywords": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "allow_search_engine_indexing": { + "type": "boolean" + }, + "notify_organizer_of_new_orders": { + "type": "boolean" + }, + "price_display_mode": { + "type": "string", + "enum": [ + "INCLUSIVE", + "EXCLUSIVE" + ] + }, + "payment_providers": { + "type": "array", + "description": "Payment settings", + "items": { + "type": "string", + "enum": [ + "STRIPE", + "OFFLINE" + ] + } + }, + "offline_payment_instructions": { + "type": [ + "string", + "null" + ] + }, + "allow_orders_awaiting_offline_payment_to_check_in": { + "type": "boolean" + }, + "enable_invoicing": { + "type": "boolean", + "description": "Invoice settings" + }, + "invoice_label": { + "type": [ + "string", + "null" + ], + "maxLength": 50 + }, + "invoice_prefix": { + "type": [ + "string", + "null" + ], + "pattern": "^[A-Za-z0-9\\-]*$", + "maxLength": 10 + }, + "invoice_start_number": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "require_billing_address": { + "type": "boolean" + }, + "organization_name": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "organization_address": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "invoice_tax_details": { + "type": [ + "string", + "null" + ] + }, + "invoice_notes": { + "type": [ + "string", + "null" + ] + }, + "invoice_payment_terms_days": { + "type": [ + "integer", + "null" + ] + }, + "ticket_design_settings": { + "type": "object", + "description": "Ticket design settings", + "properties": { + "accent_color": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "logo_image_id": { + "type": [ + "integer", + "null" + ] + }, + "footer_text": { + "type": [ + "string", + "null" + ], + "maxLength": 500 + }, + "layout_type": { + "type": [ + "string", + "null" + ], + "enum": [ + "default", + "modern", + null + ] + }, + "date_display_mode": { + "type": [ + "string", + "null" + ], + "enum": [ + "START_DATE_TIME", + "DATE_RANGE", + "HIDDEN", + null + ] + }, + "enabled": { + "type": "boolean" + } + } + }, + "show_marketing_opt_in": { + "type": "boolean", + "description": "Marketing settings" + }, + "allow_copy_details_to_all_attendees": { + "type": "boolean", + "description": "Attendee detail copy control" + }, + "pass_platform_fee_to_buyer": { + "type": "boolean", + "description": "Platform fee settings" + }, + "homepage_theme_settings": { + "type": "object", + "description": "Homepage theme settings", + "properties": { + "accent": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "background": { + "type": [ + "string", + "null" + ], + "pattern": "\\#(?:[0-9a-fA-F]{3}){1,2}$|^\\#(?:[0-9a-fA-F]{4}){1,2}$", + "minLength": 4, + "maxLength": 9 + }, + "mode": { + "type": [ + "string", + "null" + ], + "enum": [ + "light", + "dark", + null + ] + }, + "background_type": { + "type": [ + "string", + "null" + ], + "enum": [ + "MIRROR_COVER_IMAGE", + "COLOR", + null + ] + }, + "font_family": { + "type": [ + "string", + "null" + ], + "enum": [ + "Outfit", + "Inter", + "Roboto", + "Open Sans", + "Poppins", + "Montserrat", + "Lato", + "Nunito", + "Raleway", + "DM Sans", + "Plus Jakarta Sans", + "Work Sans", + "Space Grotesk", + "Manrope", + "Oswald", + "Bebas Neue", + "Playfair Display", + "Merriweather", + "Lora", + null + ] + } + } + }, + "homepage_sections": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "maxLength": 40 + }, + "type": { + "type": "string", + "enum": [ + "TICKETS", + "ABOUT", + "LINEUP", + "SCHEDULE", + "LOCATION", + "ORGANIZER", + "TEXT", + "GALLERY", + "FAQ", + "VIDEO", + "SPONSORS" + ] + }, + "enabled": { + "type": "boolean" + }, + "heading": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "body": { + "type": [ + "string", + "null" + ], + "maxLength": 50000 + }, + "faqs": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "question": { + "type": "string", + "maxLength": 300 + }, + "answer": { + "type": [ + "string", + "null" + ], + "maxLength": 5000 + } + }, + "required": [ + "question" + ] + }, + "maxItems": 50 + }, + "video_url": { + "type": [ + "string", + "null" + ], + "format": "uri", + "maxLength": 500 + }, + "image_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer" + }, + "maxItems": 40 + }, + "sponsors": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "image_id": { + "type": "integer" + }, + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "url": { + "type": [ + "string", + "null" + ], + "format": "uri", + "maxLength": 500 + } + }, + "required": [ + "image_id" + ] + }, + "maxItems": 60 + } + }, + "required": [ + "id", + "type" + ] + }, + "maxItems": 40 + }, + "allow_attendee_self_edit": { + "type": "boolean", + "description": "Self-service settings" + }, + "show_available_occurrence_capacity": { + "type": "boolean", + "description": "Occurrence display" + }, + "hide_sold_out_occurrences": { + "type": "boolean" + }, + "waitlist_auto_process": { + "type": "boolean", + "description": "Waitlist settings" + }, + "waitlist_offer_timeout_minutes": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 10080 + } + }, + "title": "UpdateEventSettingsRequest" + }, + "UpdateEventStatusRequest": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "DRAFT", + "LIVE", + "ARCHIVED" + ] + } + }, + "required": [ + "status" + ], + "title": "UpdateEventStatusRequest" + }, + "UpdateLineupEntryRequest": { + "type": "object", + "properties": { + "billing_tier": { + "$ref": "#/components/schemas/LineupBillingTier" + }, + "role_label": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "is_hidden": { + "type": "boolean" + } + }, + "required": [ + "billing_tier" + ], + "title": "UpdateLineupEntryRequest" + }, + "UpdateOrganizerLocationRequest": { + "type": "object", + "properties": { + "location_id": { + "type": [ + "integer", + "null" + ] + } + }, + "title": "UpdateOrganizerLocationRequest" + }, + "UpdateOrganizerStatusRequest": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "DRAFT", + "LIVE", + "ARCHIVED" + ] + } + }, + "required": [ + "status" + ], + "title": "UpdateOrganizerStatusRequest" + }, + "UpdateProductVisibilityRequest": { + "type": "object", + "properties": { + "product_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1 + } + }, + "required": [ + "product_ids" + ], + "title": "UpdateProductVisibilityRequest" + }, + "UpsertBoxOfficeRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 2000 + }, + "product_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "check_in_list_id": { + "type": [ + "integer", + "null" + ] + }, + "allow_price_override": { + "type": [ + "boolean", + "null" + ] + }, + "allow_discounts": { + "type": [ + "boolean", + "null" + ] + }, + "collect_order_questions": { + "type": [ + "boolean", + "null" + ] + }, + "activates_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + "required": [ + "name" + ], + "title": "UpsertBoxOfficeRequest" + }, + "UpsertCapacityAssignmentRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "capacity": { + "type": [ + "number", + "null" + ], + "minimum": 1 + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "INACTIVE" + ] + }, + "product_ids": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "required": [ + "name", + "status", + "product_ids" + ], + "title": "UpsertCapacityAssignmentRequest" + }, + "UpsertCheckInListRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 2000 + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "activates_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "product_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "public_show_attendee_notes": { + "type": [ + "boolean", + "null" + ] + }, + "public_show_question_answers": { + "type": [ + "boolean", + "null" + ] + }, + "public_show_order_details": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "name" + ], + "title": "UpsertCheckInListRequest" + }, + "UpsertEventOccurrenceRequest": { + "type": "object", + "properties": { + "start_date": { + "type": "string", + "format": "date-time" + }, + "end_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "capacity": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "label": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "show_available_capacity": { + "type": [ + "boolean", + "null" + ] + }, + "event_location": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "IN_PERSON", + "ONLINE" + ] + }, + "location_id": { + "type": [ + "integer", + "null" + ] + }, + "online_event_connection_details": { + "type": [ + "string", + "null" + ], + "maxLength": 10000 + } + } + }, + "clear_event_location": { + "type": "boolean" + } + }, + "required": [ + "start_date" + ], + "title": "UpsertEventOccurrenceRequest" + }, + "UpsertEventSpaceRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "event_location_id": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "name" + ], + "title": "UpsertEventSpaceRequest" + }, + "UpsertEventWidgetRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "type": { + "type": "string", + "enum": [ + "TICKETS", + "BUTTON", + "LINEUP", + "SCHEDULE" + ] + }, + "settings": { + "type": "object", + "properties": { + "background_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "primary_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "primary_text_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "secondary_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "secondary_text_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "padding": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 500 + }, + "radius": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 32 + }, + "continue_button_text": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "button_text": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "checkout": { + "type": [ + "string", + "null" + ], + "enum": [ + "modal", + "new-tab", + null + ] + }, + "autoresize": { + "type": [ + "boolean", + "null" + ] + }, + "show_lineup": { + "type": [ + "boolean", + "null" + ] + }, + "hide_promo_code": { + "type": [ + "boolean", + "null" + ] + }, + "hide_category_titles": { + "type": [ + "boolean", + "null" + ] + }, + "theme": { + "type": [ + "string", + "null" + ], + "enum": [ + "custom", + "event", + null + ] + }, + "font": { + "type": [ + "string", + "null" + ], + "enum": [ + "Outfit", + "Inter", + "Roboto", + "Open Sans", + "Poppins", + "Montserrat", + "Lato", + "Nunito", + "Raleway", + "DM Sans", + "Plus Jakarta Sans", + "Work Sans", + "Space Grotesk", + "Manrope", + "Oswald", + "Bebas Neue", + "Playfair Display", + "Merriweather", + "Lora", + null + ] + }, + "locale": { + "type": [ + "string", + "null" + ], + "enum": [ + "en", + "de", + "fi", + "fr", + "it", + "nl", + "hu", + "es", + "pt", + "pt-br", + "zh-cn", + "se", + "zh-hk", + "vi", + "tr", + "pl", + "sk", + "el", + "ko", + "ja", + null + ] + }, + "sections": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "tickets", + "header", + "about", + "location", + "organizer", + "lineup", + "schedule", + "custom" + ] + }, + "maxItems": 8 + }, + "promo_code": { + "type": [ + "string", + "null" + ], + "maxLength": 50 + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer", + "minimum": 1 + }, + "maxItems": 100 + }, + "categories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer", + "minimum": 1 + }, + "maxItems": 100 + }, + "occurrence_id": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "layout": { + "type": [ + "string", + "null" + ], + "enum": [ + "list", + "grid", + "compact", + null + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 50 + }, + "event_category": { + "type": [ + "string", + "null" + ], + "enum": [ + "SOCIAL", + "FAMILY", + "HOBBIES", + "FOOD_DRINK", + "WELLNESS", + "SPIRITUALITY", + "OUTDOORS", + "TOURS", + "CHARITY", + "MUSIC", + "ART", + "COMEDY", + "THEATER", + "FILM", + "DANCE", + "BUSINESS", + "TECH", + "EDUCATION", + "WORKSHOP", + "SPORTS", + "FESTIVAL", + "SEASONAL", + "NIGHTLIFE", + "OTHER", + null + ] + }, + "show_past": { + "type": [ + "boolean", + "null" + ] + }, + "open_in": { + "type": [ + "string", + "null" + ], + "enum": [ + "popup", + "page", + null + ] + } + }, + "required": [ + "background_color", + "primary_color", + "primary_text_color", + "secondary_color", + "secondary_text_color", + "padding", + "radius", + "continue_button_text", + "button_text", + "checkout", + "autoresize", + "show_lineup", + "hide_promo_code", + "hide_category_titles", + "theme", + "font", + "locale", + "sections", + "promo_code", + "products", + "categories", + "occurrence_id" + ] + } + }, + "required": [ + "name", + "type" + ], + "title": "UpsertEventWidgetRequest" + }, + "UpsertLocationRequest": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "structured_address": { + "type": "object", + "properties": { + "venue_name": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "address_line_1": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "address_line_2": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "city": { + "type": [ + "string", + "null" + ], + "maxLength": 85 + }, + "state_or_region": { + "type": [ + "string", + "null" + ], + "maxLength": 85 + }, + "zip_or_postal_code": { + "type": [ + "string", + "null" + ], + "maxLength": 85 + }, + "country": { + "type": [ + "string", + "null" + ], + "minLength": 2, + "maxLength": 2 + } + } + }, + "latitude": { + "type": [ + "number", + "null" + ], + "minimum": -90, + "maximum": 90 + }, + "longitude": { + "type": [ + "number", + "null" + ], + "minimum": -180, + "maximum": 180 + }, + "provider": { + "type": [ + "string", + "null" + ], + "enum": [ + "google", + null + ] + }, + "provider_place_id": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + } + }, + "required": [ + "structured_address" + ], + "title": "UpsertLocationRequest" + }, + "UpsertOrganizerRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "email": { + "type": "string", + "format": "email" + }, + "phone": { + "type": [ + "string", + "null" + ], + "maxLength": 25 + }, + "website": { + "type": [ + "string", + "null" + ], + "format": "uri", + "maxLength": 255 + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 1200 + }, + "timezone": { + "type": "string" + }, + "currency": { + "type": "string", + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "AOA", + "ARS", + "AUD", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "GBP", + "GEL", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "STD", + "SVC", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VND", + "VUV", + "WST", + "XCD", + "XAF", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW" + ] + } + }, + "required": [ + "name", + "email", + "timezone", + "currency" + ], + "title": "UpsertOrganizerRequest" + }, + "UpsertOrganizerWidgetRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "type": { + "type": "string", + "enum": [ + "EVENTS" + ] + }, + "settings": { + "type": "object", + "properties": { + "background_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "primary_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "primary_text_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "secondary_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "secondary_text_color": { + "type": [ + "string", + "null" + ], + "pattern": "^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$" + }, + "padding": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 500 + }, + "radius": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 32 + }, + "continue_button_text": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "button_text": { + "type": [ + "string", + "null" + ], + "maxLength": 100 + }, + "checkout": { + "type": [ + "string", + "null" + ], + "enum": [ + "modal", + "new-tab", + null + ] + }, + "autoresize": { + "type": [ + "boolean", + "null" + ] + }, + "show_lineup": { + "type": [ + "boolean", + "null" + ] + }, + "hide_promo_code": { + "type": [ + "boolean", + "null" + ] + }, + "hide_category_titles": { + "type": [ + "boolean", + "null" + ] + }, + "theme": { + "type": [ + "string", + "null" + ], + "enum": [ + "custom", + "event", + null + ] + }, + "font": { + "type": [ + "string", + "null" + ], + "enum": [ + "Outfit", + "Inter", + "Roboto", + "Open Sans", + "Poppins", + "Montserrat", + "Lato", + "Nunito", + "Raleway", + "DM Sans", + "Plus Jakarta Sans", + "Work Sans", + "Space Grotesk", + "Manrope", + "Oswald", + "Bebas Neue", + "Playfair Display", + "Merriweather", + "Lora", + null + ] + }, + "locale": { + "type": [ + "string", + "null" + ], + "enum": [ + "en", + "de", + "fi", + "fr", + "it", + "nl", + "hu", + "es", + "pt", + "pt-br", + "zh-cn", + "se", + "zh-hk", + "vi", + "tr", + "pl", + "sk", + "el", + "ko", + "ja", + null + ] + }, + "sections": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "tickets", + "header", + "about", + "location", + "organizer", + "lineup", + "schedule", + "custom" + ] + }, + "maxItems": 8 + }, + "promo_code": { + "type": [ + "string", + "null" + ], + "maxLength": 50 + }, + "products": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer", + "minimum": 1 + }, + "maxItems": 100 + }, + "categories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer", + "minimum": 1 + }, + "maxItems": 100 + }, + "occurrence_id": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "layout": { + "type": [ + "string", + "null" + ], + "enum": [ + "list", + "grid", + "compact", + null + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 50 + }, + "event_category": { + "type": [ + "string", + "null" + ], + "enum": [ + "SOCIAL", + "FAMILY", + "HOBBIES", + "FOOD_DRINK", + "WELLNESS", + "SPIRITUALITY", + "OUTDOORS", + "TOURS", + "CHARITY", + "MUSIC", + "ART", + "COMEDY", + "THEATER", + "FILM", + "DANCE", + "BUSINESS", + "TECH", + "EDUCATION", + "WORKSHOP", + "SPORTS", + "FESTIVAL", + "SEASONAL", + "NIGHTLIFE", + "OTHER", + null + ] + }, + "show_past": { + "type": [ + "boolean", + "null" + ] + }, + "open_in": { + "type": [ + "string", + "null" + ], + "enum": [ + "popup", + "page", + null + ] + } + }, + "required": [ + "background_color", + "primary_color", + "primary_text_color", + "secondary_color", + "secondary_text_color", + "padding", + "radius", + "continue_button_text", + "button_text", + "checkout", + "autoresize", + "show_lineup", + "hide_promo_code", + "hide_category_titles", + "theme", + "font", + "locale", + "sections", + "promo_code", + "products", + "categories", + "occurrence_id" + ] + } + }, + "required": [ + "name", + "type" + ], + "title": "UpsertOrganizerWidgetRequest" + }, + "UpsertPriceOverrideRequest": { + "type": "object", + "properties": { + "product_price_id": { + "type": "integer" + }, + "price": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "maximum": 100000000 + }, + "quantity_available": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 2147483647 + } + }, + "required": [ + "product_price_id" + ], + "title": "UpsertPriceOverrideRequest" + }, + "UpsertProductCategoryRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 50 + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 5000 + }, + "is_hidden": { + "type": "boolean" + }, + "no_products_message": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + } + }, + "required": [ + "name", + "is_hidden" + ], + "title": "UpsertProductCategoryRequest" + }, + "UpsertProductRequest": { + "type": "object", + "properties": { + "title": { + "type": "string", + "maxLength": 255 + }, + "initial_quantity_available": { + "type": [ + "integer", + "null" + ], + "maximum": 2147483647 + }, + "quantity_sold": { + "type": "integer", + "maximum": 2147483647 + }, + "sale_start_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "sale_end_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "max_per_order": { + "type": [ + "integer", + "null" + ], + "maximum": 2147483647 + }, + "prices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "price": { + "type": "number", + "maximum": 999999999999 + }, + "label": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "maxLength": 100 + }, + "sale_start_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "sale_end_date": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "initial_quantity_available": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 2147483647 + }, + "is_hidden": { + "type": "boolean" + }, + "quantity_applies_to": { + "type": [ + "string", + "null" + ], + "enum": [ + "OCCURRENCE", + "EVENT", + null + ] + } + }, + "required": [ + "price" + ] + }, + "minItems": 1 + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "min_per_order": { + "type": [ + "integer", + "null" + ], + "maximum": 2147483647 + }, + "is_hidden": { + "type": "boolean" + }, + "hide_before_sale_start_date": { + "type": "boolean" + }, + "hide_after_sale_end_date": { + "type": "boolean" + }, + "hide_when_sold_out": { + "type": "boolean" + }, + "sequential_tier_release_enabled": { + "type": "boolean" + }, + "start_collapsed": { + "type": "boolean" + }, + "show_quantity_remaining": { + "type": "boolean" + }, + "is_hidden_without_promo_code": { + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "PAID", + "FREE", + "DONATION", + "TIERED", + "REGISTRATION" + ] + }, + "product_type": { + "type": "string", + "enum": [ + "TICKET", + "GENERAL" + ] + }, + "tax_and_fee_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "addon_product_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "is_addon_only": { + "type": "boolean" + }, + "product_category_id": { + "type": "integer" + }, + "is_highlighted": { + "type": "boolean" + }, + "highlight_message": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "waitlist_enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "title", + "prices", + "type", + "product_type", + "product_category_id" + ], + "title": "UpsertProductRequest" + }, + "UpsertProfileRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "tagline": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "bio": { + "type": [ + "string", + "null" + ], + "maxLength": 20000 + }, + "links": { + "type": "array", + "items": { + "type": "string", + "format": "uri", + "maxLength": 500 + }, + "maxItems": 12 + } + }, + "required": [ + "name" + ], + "title": "UpsertProfileRequest" + }, + "UpsertScheduleItemRequest": { + "type": "object", + "properties": { + "title": { + "type": [ + "string", + "null" + ], + "maxLength": 255 + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 20000 + }, + "starts_at": { + "type": "string", + "format": "date-time" + }, + "ends_at": { + "type": "string", + "format": "date-time" + }, + "event_space_id": { + "type": [ + "integer", + "null" + ] + }, + "lineup_entry_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "maxItems": 50 + } + }, + "required": [ + "starts_at", + "ends_at" + ], + "title": "UpsertScheduleItemRequest" + }, + "UpsertSeatMapRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "version": { + "type": [ + "integer", + "null" + ] + }, + "layout": { + "type": "object", + "properties": { + "schema": { + "type": "integer" + }, + "bands": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "areas": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "required": [ + "schema", + "bands", + "areas" + ] + } + }, + "required": [ + "name", + "layout" + ], + "title": "UpsertSeatMapRequest" + }, + "UpsertWebhookRequest": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri" + }, + "event_types": { + "type": "object" + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "ENABLED", + "PAUSED", + null + ] + } + }, + "required": [ + "url", + "event_types" + ], + "title": "UpsertWebhookRequest" + }, + "UserResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "timezone": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "full_name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "is_email_verified": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "two_factor_enabled": { + "type": "boolean" + }, + "has_pending_email_change": { + "type": "boolean" + }, + "has_password": { + "type": "boolean" + }, + "marketing_opted_in_at": { + "type": [ + "string", + "null" + ] + }, + "is_impersonating": { + "type": "boolean" + }, + "impersonator_id": { + "type": "string" + }, + "enforce_email_confirmation_during_registration": { + "type": "boolean" + }, + "role": { + "type": [ + "string", + "null" + ] + }, + "is_account_owner": { + "type": [ + "boolean", + "null" + ] + }, + "last_login_at": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "account_id": { + "type": [ + "integer", + "null" + ] + }, + "notify_new_orders": { + "type": [ + "boolean", + "null" + ] + }, + "sso_sign_in": { + "type": [ + "string", + "null" + ], + "enum": [ + "SSO", + "EXEMPT", + "PASSWORD", + null + ] + }, + "access": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "enum": [ + "ACCOUNT", + "RESTRICTED" + ] + }, + "organizer_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "event_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "permissions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EDIT", + "VIEW_FINANCIALS", + "ISSUE_REFUNDS", + "MANAGE_PAYMENTS", + "DELETE_EVENTS", + "MANAGE_TEAM", + "MANAGE_ACCOUNT" + ] + } + } + }, + "required": [ + "scope", + "organizer_ids", + "event_ids", + "permissions" + ] + }, + "pending_email": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "timezone", + "first_name", + "last_name", + "full_name", + "email", + "is_email_verified", + "locale", + "two_factor_enabled" + ], + "title": "UserResource" + }, + "WaitlistEntryResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "event_id": { + "type": "integer" + }, + "product_price_id": { + "type": "integer" + }, + "event_occurrence_id": { + "type": [ + "integer", + "null" + ] + }, + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "offered_at": { + "type": [ + "string", + "null" + ] + }, + "offer_expires_at": { + "type": [ + "string", + "null" + ] + }, + "purchased_at": { + "type": [ + "string", + "null" + ] + }, + "cancelled_at": { + "type": [ + "string", + "null" + ] + }, + "order_id": { + "type": [ + "integer", + "null" + ] + }, + "locale": { + "type": "string" + }, + "product": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProductResource" + }, + { + "type": "null" + } + ] + }, + "product_price": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProductPriceResource" + }, + { + "type": "null" + } + ] + }, + "event_occurrence": { + "anyOf": [ + { + "$ref": "#/components/schemas/EventOccurrenceResource" + }, + { + "type": "null" + } + ] + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "event_id", + "product_price_id", + "event_occurrence_id", + "email", + "first_name", + "last_name", + "status", + "position", + "offered_at", + "offer_expires_at", + "purchased_at", + "cancelled_at", + "order_id", + "locale", + "product", + "product_price", + "event_occurrence", + "created_at", + "updated_at" + ], + "title": "WaitlistEntryResource" + }, + "WebhookLogResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "webhook_id": { + "type": "integer" + }, + "payload": { + "type": "string" + }, + "response_body": { + "type": [ + "string", + "null" + ] + }, + "response_code": { + "type": [ + "integer", + "null" + ] + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "event_type": { + "type": "string" + } + }, + "required": [ + "id", + "webhook_id", + "payload", + "response_body", + "response_code", + "created_at", + "event_type" + ], + "title": "WebhookLogResource" + }, + "WebhookResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "event_types": { + "anyOf": [ + { + "type": "array", + "items": [] + }, + { + "type": "string" + } + ] + }, + "status": { + "type": "string" + }, + "last_triggered_at": { + "type": [ + "string", + "null" + ] + }, + "last_response_body": { + "type": [ + "string", + "null" + ] + }, + "last_response_code": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "id", + "url", + "event_types", + "status", + "last_triggered_at", + "last_response_body", + "last_response_code" + ], + "title": "WebhookResource" + }, + "WebhookResourceWithSecret": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "event_types": { + "anyOf": [ + { + "type": "array", + "items": [] + }, + { + "type": "string" + } + ] + }, + "status": { + "type": "string" + }, + "last_triggered_at": { + "type": [ + "string", + "null" + ] + }, + "last_response_body": { + "type": [ + "string", + "null" + ] + }, + "last_response_code": { + "type": [ + "integer", + "null" + ] + }, + "secret": { + "type": "string" + } + }, + "required": [ + "id", + "url", + "event_types", + "status", + "last_triggered_at", + "last_response_body", + "last_response_code", + "secret" + ], + "title": "WebhookResourceWithSecret" + }, + "WidgetResource": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "short_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "TICKETS", + "BUTTON", + "LINEUP", + "SCHEDULE", + "EVENTS" + ] + }, + "event_id": { + "type": [ + "integer", + "null" + ] + }, + "organizer_id": { + "type": "integer" + }, + "settings": { + "type": "string" + }, + "created_at": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": [ + "string", + "null" + ] + }, + "stats": { + "type": "object", + "properties": { + "views": { + "type": "integer" + }, + "orders": { + "type": "integer" + }, + "revenue": { + "type": "string" + } + }, + "required": [ + "views", + "orders" + ] + } + }, + "required": [ + "id", + "short_id", + "name", + "type", + "event_id", + "organizer_id", + "settings", + "created_at", + "updated_at", + "stats" + ], + "title": "WidgetResource" + }, + "WidgetResourcePublic": { + "type": "object", + "properties": { + "short_id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "TICKETS", + "BUTTON", + "LINEUP", + "SCHEDULE", + "EVENTS" + ] + }, + "event_id": { + "type": [ + "integer", + "null" + ] + }, + "organizer_id": { + "type": "integer" + }, + "settings": { + "type": "string" + } + }, + "required": [ + "short_id", + "type", + "event_id", + "organizer_id", + "settings" + ], + "title": "WidgetResourcePublic" + } + }, + "responses": { + "ValidationException": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Errors overview." + }, + "errors": { + "type": "object", + "description": "A detailed description of each field that failed validation.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "required": [ + "message", + "errors" + ] + } + } + } + }, + "AuthenticationException": { + "description": "Unauthenticated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error overview." + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } +} diff --git a/backend/resources/views/emails/api-keys/api-key-created.blade.php b/backend/resources/views/emails/api-keys/api-key-created.blade.php new file mode 100644 index 0000000000..7a432bd284 --- /dev/null +++ b/backend/resources/views/emails/api-keys/api-key-created.blade.php @@ -0,0 +1,25 @@ +@php /** @var string $keyName */ @endphp +@php /** @var string $accountName */ @endphp +@php /** @var string $accessLabel */ @endphp +@php /** @var string|null $expiresAt */ @endphp +@php /** @var string $apiKeysUrl */ @endphp + + +{{ __('Hi') }}, + +{{ __('A new API key was created on your :appName account. Anyone with this key can use the API with your access to :accountName.', ['appName' => config('app.name'), 'accountName' => $accountName]) }} + + +**{{ __('Name') }}:** {{ $keyName }}
+**{{ __('Access') }}:** {{ $accessLabel }}
+**{{ __('Expires') }}:** {{ $expiresAt ?? __('Never') }} +
+ + +{{ __('Review API keys') }} + + +{{ __('If you did not create this key, revoke it now, change your password and contact support.') }} + +{{ __('Thanks,') }} +
diff --git a/backend/resources/views/emails/api-keys/api-key-expiring.blade.php b/backend/resources/views/emails/api-keys/api-key-expiring.blade.php new file mode 100644 index 0000000000..8c382cc770 --- /dev/null +++ b/backend/resources/views/emails/api-keys/api-key-expiring.blade.php @@ -0,0 +1,18 @@ +@php /** @var string $keyName */ @endphp +@php /** @var string $accountName */ @endphp +@php /** @var string $expiresAt */ @endphp +@php /** @var string $apiKeysUrl */ @endphp + + +{{ __('Hi') }}, + +{{ __('Your API key ":keyName" for :accountName expires on :date. After that, integrations that use it can no longer connect.', ['keyName' => $keyName, 'accountName' => $accountName, 'date' => $expiresAt]) }} + +{{ __('To avoid an interruption, create a new key and update your integration before then.') }} + + +{{ __('Create a new key') }} + + +{{ __('Thanks,') }} + diff --git a/backend/resources/views/emails/api-keys/api-key-revoked.blade.php b/backend/resources/views/emails/api-keys/api-key-revoked.blade.php new file mode 100644 index 0000000000..f150bffe7d --- /dev/null +++ b/backend/resources/views/emails/api-keys/api-key-revoked.blade.php @@ -0,0 +1,17 @@ +@php /** @var string $keyName */ @endphp +@php /** @var string $accountName */ @endphp +@php /** @var string $apiKeysUrl */ @endphp + + +{{ __('Hi') }}, + +{{ __('An administrator of :accountName revoked your API key ":keyName". Integrations that use it can no longer connect.', ['accountName' => $accountName, 'keyName' => $keyName]) }} + +{{ __('If you still need API access, create a new key and update your integration.') }} + + +{{ __('Manage API keys') }} + + +{{ __('Thanks,') }} + diff --git a/backend/routes/api.php b/backend/routes/api.php index 31ec34fa57..466661e9f4 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -130,6 +130,10 @@ use HiEvents\Http\Actions\Affiliates\UpdateAffiliateAction; use HiEvents\Http\Actions\Announcements\DismissAnnouncementAction; use HiEvents\Http\Actions\Announcements\GetActiveAnnouncementsAction; +use HiEvents\Http\Actions\ApiKeys\CreateApiKeyAction; +use HiEvents\Http\Actions\ApiKeys\DeleteApiKeyAction; +use HiEvents\Http\Actions\ApiKeys\GetApiKeysAction; +use HiEvents\Http\Actions\ApiReference\GetApiReferenceAction; use HiEvents\Http\Actions\Attendees\CheckInAttendeeAction; use HiEvents\Http\Actions\Attendees\CreateAttendeeAction; use HiEvents\Http\Actions\Attendees\EditAttendeeAction; @@ -431,17 +435,13 @@ function (Router $router): void { ); /** - * Logged In Routes + * Signed-in routes that API keys can't use */ $router->middleware(['auth:api'])->group( function (Router $router): void { - // Auth $router->get('/auth/logout', LogoutAction::class); $router->post('/auth/refresh', RefreshTokenAction::class); $router->post('/auth/invitation/{invite_token}/join', JoinInvitationAction::class); - - // Users - $router->get('/users/me', GetMeAction::class); $router->put('/users/me', UpdateMeAction::class)->middleware('throttle:user-update-me'); $router->get('/users/me/two-factor', GetTwoFactorStatusAction::class); $router->post('/users/me/two-factor/setup', BeginTwoFactorSetupAction::class)->middleware('throttle:10,1,two-factor-setup'); @@ -457,8 +457,8 @@ function (Router $router): void { $router->delete('/users/me/sso-identities/{identity_id}', DeleteSsoIdentityAction::class)->whereNumber('identity_id')->middleware('throttle:auth-sso-start'); $router->post('/users', CreateUserAction::class)->middleware('throttle:user-invite'); $router->get('/users', GetUsersAction::class); - $router->get('/users/{user_id}', GetUserAction::class); - $router->put('/users/{user_id}', UpdateUserAction::class); + $router->get('/users/{user_id}', GetUserAction::class)->whereNumber('user_id'); + $router->put('/users/{user_id}', UpdateUserAction::class)->whereNumber('user_id'); $router->post('/users/{user_id}/email-change/{changeToken}', ConfirmEmailChangeAction::class); $router->post('/users/{user_id}/invitation', ResendInvitationAction::class)->middleware('throttle:user-invite'); $router->delete('/users/{user_id}/invitation', DeleteInvitationAction::class); @@ -467,12 +467,8 @@ function (Router $router): void { $router->post('/users/{user_id}/resend-email-confirmation', ResendEmailConfirmationAction::class)->middleware('throttle:user-email-confirmation'); $router->post('/users/{user_id}/confirm-email-with-code', ConfirmEmailWithCodeAction::class)->middleware('throttle:user-email-confirmation-code'); $router->post('/users/{user_id}/two-factor/reset', ResetAccountUserTwoFactorAction::class)->whereNumber('user_id'); - - // Announcements $router->get('/announcements/active', GetActiveAnnouncementsAction::class); $router->post('/announcements/{announcement_id}/dismiss', DismissAnnouncementAction::class); - - // Accounts $router->post('/accounts/deletion-request', RequestAccountDeletionAction::class); $router->delete('/accounts/deletion-request', CancelAccountDeletionAction::class); $router->get('/accounts/deletion-request', GetAccountDeletionStatusAction::class); @@ -490,8 +486,37 @@ function (Router $router): void { $router->get('/accounts/{account_id}/sso/enforcement', GetSsoEnforcementPreviewAction::class)->whereNumber('account_id'); $router->put('/accounts/{account_id}/sso/enforcement', UpdateSsoEnforcementAction::class)->whereNumber('account_id'); $router->put('/accounts/{account_id}/sso/members/{account_user_id}', UpdateSsoMemberEnforcementAction::class)->whereNumber(['account_id', 'account_user_id']); - $router->get('/accounts/{account_id?}', GetAccountAction::class); - $router->put('/accounts/{account_id?}', UpdateAccountAction::class); + $router->put('/accounts/{account_id?}', UpdateAccountAction::class)->whereNumber('account_id'); + $router->get('/organizers/{organizer_id}/team', GetOrganizerTeamAction::class); + $router->get('/organizers/{organizerId}/stripe/connect_accounts', GetStripeConnectAccountsAction::class); + $router->post('/organizers/{organizerId}/stripe/connect', CreateStripeConnectAccountAction::class); + $router->post('/organizers/{organizerId}/stripe/copy_from/{sourceOrganizerId}', CopyStripeConnectAccountAction::class); + $router->delete('/organizers/{organizerId}/stripe/connect_accounts/{stripeAccountId}', DisconnectStripeConnectAccountAction::class) + ->where('stripeAccountId', '[A-Za-z0-9_]+'); + $router->get('/organizers/{organizerId}/stripe/terminal/readers', GetStripeTerminalReadersAction::class); + $router->post('/organizers/{organizerId}/stripe/terminal/readers', RegisterStripeTerminalReaderAction::class) + ->middleware('ee.licensed:box_office'); + $router->delete('/organizers/{organizerId}/stripe/terminal/readers/{readerId}', DeleteStripeTerminalReaderAction::class); + $router->post('/organizers/{organizerId}/vat-settings', UpsertOrganizerVatSettingAction::class); + $router->get('/events/{event_id}/team', GetEventTeamAction::class); + + $router->get('/api-reference', GetApiReferenceAction::class); + $router->get('/api-keys', GetApiKeysAction::class); + $router->post('/api-keys', CreateApiKeyAction::class)->middleware('throttle:10,1,api-key-create'); + $router->delete('/api-keys/{api_key_id}', DeleteApiKeyAction::class)->whereNumber('api_key_id'); + } +); + +/** + * Signed-in routes, also available to API keys + */ +$router->middleware(['auth:api,sanctum'])->group( + function (Router $router): void { + // Users + $router->get('/users/me', GetMeAction::class); + + // Accounts + $router->get('/accounts/{account_id?}', GetAccountAction::class)->whereNumber('account_id'); // Organizers $router->post('/organizers', CreateOrganizerAction::class); @@ -504,7 +529,6 @@ function (Router $router): void { $router->get('/organizers/{organizer_id}', GetOrganizerAction::class); $router->get('/organizers/{organizer_id}/events', GetOrganizerEventsAction::class); $router->get('/organizers/{organizer_id}/stats', GetOrganizerStatsAction::class); - $router->get('/organizers/{organizer_id}/team', GetOrganizerTeamAction::class); $router->get('/organizers/{organizer_id}/orders', GetOrganizerOrdersAction::class); $router->get('/organizers/{organizer_id}/settings', GetOrganizerSettingsAction::class); $router->patch('/organizers/{organizer_id}/settings', PartialUpdateOrganizerSettingsAction::class); @@ -568,20 +592,8 @@ function (Router $router): void { $router->post('/events/{event_id}/seat-blocks/release', ReleaseSeatBlocksAction::class); $router->put('/events/{event_id}/attendees/{attendee_id}/seat', MoveAttendeeSeatAction::class); - // Stripe Connect - Organizer level - $router->get('/organizers/{organizerId}/stripe/connect_accounts', GetStripeConnectAccountsAction::class); - $router->post('/organizers/{organizerId}/stripe/connect', CreateStripeConnectAccountAction::class); - $router->post('/organizers/{organizerId}/stripe/copy_from/{sourceOrganizerId}', CopyStripeConnectAccountAction::class); - $router->delete('/organizers/{organizerId}/stripe/connect_accounts/{stripeAccountId}', DisconnectStripeConnectAccountAction::class) - ->where('stripeAccountId', '[A-Za-z0-9_]+'); - $router->get('/organizers/{organizerId}/stripe/terminal/readers', GetStripeTerminalReadersAction::class); - $router->post('/organizers/{organizerId}/stripe/terminal/readers', RegisterStripeTerminalReaderAction::class) - ->middleware('ee.licensed:box_office'); - $router->delete('/organizers/{organizerId}/stripe/terminal/readers/{readerId}', DeleteStripeTerminalReaderAction::class); - // VAT Settings - Organizer level $router->get('/organizers/{organizerId}/vat-settings', GetOrganizerVatSettingAction::class); - $router->post('/organizers/{organizerId}/vat-settings', UpsertOrganizerVatSettingAction::class); // Email Templates - Organizer level $router->get('/organizers/{organizerId}/email-templates', GetOrganizerEmailTemplatesAction::class); @@ -629,7 +641,6 @@ function (Router $router): void { // Stats $router->get('/events/{event_id}/stats', GetEventStatsAction::class); - $router->get('/events/{event_id}/team', GetEventTeamAction::class); $router->get('/events/{event_id}/counts', GetEventCountsAction::class); // Email Templates - Event level diff --git a/backend/tests/Concerns/UsesApiKeys.php b/backend/tests/Concerns/UsesApiKeys.php new file mode 100644 index 0000000000..bc45e17a3f --- /dev/null +++ b/backend/tests/Concerns/UsesApiKeys.php @@ -0,0 +1,38 @@ +create( + user: app(UserRepositoryInterface::class)->findById($user->id), + accountId: $accountId, + name: $name, + access: $access, + expiresInDays: $expiresInDays, + )->plainTextKey; + } + + protected function apiKeyHeaders(string $apiKey): array + { + $this->app['auth']->forgetGuards(); + $this->app['tymon.jwt']->unsetToken(); + + return ['Authorization' => 'Bearer '.$apiKey]; + } +} diff --git a/backend/tests/Feature/Auth/Sso/SsoDomainCaptureTest.php b/backend/tests/Feature/Auth/Sso/SsoDomainCaptureTest.php new file mode 100644 index 0000000000..9b020bbc82 --- /dev/null +++ b/backend/tests/Feature/Auth/Sso/SsoDomainCaptureTest.php @@ -0,0 +1,128 @@ + 1], [ + 'id' => 1, + 'name' => 'Default', + 'is_system_default' => true, + 'application_fees' => ['percentage' => 1.5, 'fixed' => 0], + ]); + config()->set('app.disable_registration', false); + config()->set('app.saas_mode_enabled', true); + $this->fakeOidcProvider(); + } + + public function test_password_signup_on_a_domain_that_requires_sso_is_sent_to_sso(): void + { + $this->accountRequiringSso(); + + $this->postJson('/auth/register', $this->registration('Grace@ACME.test')) + ->assertForbidden() + ->assertJsonPath('error_code', 'SSO_REQUIRED') + ->assertJsonPath('context.email', 'Grace@ACME.test'); + + $this->assertDatabaseMissing('users', ['email' => 'grace@acme.test']); + } + + public function test_password_signup_stays_open_when_the_account_does_not_require_sso(): void + { + $this->accountRequiringSso(enforce: false); + + $this->postJson('/auth/register', $this->registration('grace@acme.test'))->assertCreated(); + } + + public function test_password_signup_stays_open_when_the_domain_is_suspended(): void + { + $this->accountRequiringSso(failingSince: now()->subDays(8)->toDateTimeString()); + + $this->postJson('/auth/register', $this->registration('grace@acme.test'))->assertCreated(); + } + + public function test_social_signup_on_a_domain_that_requires_sso_is_refused_before_a_ticket_is_issued(): void + { + $this->accountRequiringSso(); + $flow = $this->startOidcFlow(['intent' => 'SIGNUP']); + + $this->exchangeOidcCode($flow, [ + 'sub' => 'new-subject', + 'email' => 'grace@acme.test', + 'email_verified' => true, + 'name' => 'Grace Hopper', + ]) + ->assertForbidden() + ->assertJsonPath('error_code', 'SSO_REQUIRED') + ->assertJsonPath('context.email', 'grace@acme.test'); + + $this->assertDatabaseMissing('users', ['email' => 'grace@acme.test']); + } + + public function test_a_social_signup_ticket_issued_before_sso_was_required_cannot_create_the_account(): void + { + $connection = $this->accountRequiringSso(enforce: false); + $flow = $this->startOidcFlow(['intent' => 'SIGNUP']); + $ticket = $this->exchangeOidcCode($flow, [ + 'sub' => 'new-subject', + 'email' => 'grace@acme.test', + 'email_verified' => true, + 'name' => 'Grace Hopper', + ])->assertOk()->json('data.signup.ticket'); + + $connection->update(['enforce_sso' => true]); + + $this->postJson('/auth/sso/signup', [ + 'signup_ticket' => $ticket, + 'binding' => $flow['binding'], + 'first_name' => 'Grace', + 'timezone' => 'Europe/Dublin', + 'currency_code' => 'EUR', + 'locale' => 'en', + ]) + ->assertForbidden() + ->assertJsonPath('error_code', 'SSO_REQUIRED'); + + $this->assertDatabaseMissing('users', ['email' => 'grace@acme.test']); + } + + private function accountRequiringSso(bool $enforce = true, ?string $failingSince = null): AccountSsoConnection + { + $accountId = User::factory()->withAccount()->create()->accounts()->first()->id; + $this->addDomain($accountId, 'acme.test', failingSince: $failingSince); + + return $this->createConnection($accountId, ['enforce_sso' => $enforce]); + } + + private function registration(string $email): array + { + return [ + 'first_name' => 'Grace', + 'last_name' => 'Hopper', + 'email' => $email, + 'password' => self::PASSWORD, + 'password_confirmation' => self::PASSWORD, + 'timezone' => 'Europe/Dublin', + 'currency_code' => 'EUR', + 'locale' => 'en', + ]; + } +} diff --git a/backend/tests/Feature/Auth/Sso/SsoEnforcementTest.php b/backend/tests/Feature/Auth/Sso/SsoEnforcementTest.php index f1b242cdbc..7d8dfedc0a 100644 --- a/backend/tests/Feature/Auth/Sso/SsoEnforcementTest.php +++ b/backend/tests/Feature/Auth/Sso/SsoEnforcementTest.php @@ -16,6 +16,7 @@ use PragmaRX\Google2FA\Google2FA; use Tests\Concerns\ActsAsAccountUsers; use Tests\Concerns\CreatesSsoConnections; +use Tests\Concerns\UsesApiKeys; use Tests\TestCase; class SsoEnforcementTest extends TestCase @@ -23,6 +24,7 @@ class SsoEnforcementTest extends TestCase use ActsAsAccountUsers; use CreatesSsoConnections; use DatabaseTransactions; + use UsesApiKeys; private const PASSWORD = 'CorrectHorse123!'; @@ -107,6 +109,25 @@ public function test_a_session_ended_by_requiring_sso_says_why(): void ->assertJsonPath('error_code', 'SSO_REQUIRED'); } + public function test_enabling_removes_a_members_api_keys_in_this_account_only(): void + { + [$owner, $accountId] = $this->enforcingAccount(enabled: false); + $member = $this->member($accountId, 'grace@acme.test'); + $otherAccountId = User::factory()->withAccount()->create()->accounts()->first()->id; + $member->accounts()->attach($otherAccountId, [ + 'role' => Role::ADMIN->value, + 'status' => UserStatus::ACTIVE->name, + 'is_account_owner' => false, + ]); + $this->createApiKey($member, $accountId, name: 'Here'); + $this->createApiKey($member, $otherAccountId, name: 'Elsewhere'); + + $this->putJson("/accounts/$accountId/sso/enforcement", ['enabled' => true], $this->bearer($this->tokenFor($owner, $accountId))) + ->assertOk(); + + $this->assertSame(['Elsewhere'], DB::table('personal_access_tokens')->where('tokenable_id', $member->id)->pluck('name')->all()); + } + public function test_an_enforced_member_cannot_sign_in_with_a_password_but_can_through_the_connection(): void { [, $accountId, $connection] = $this->enforcingAccount(); @@ -241,7 +262,8 @@ public function test_the_owner_is_told_two_factor_must_stay_on_while_the_account $this->getJson('/users/me/two-factor', $this->bearer($this->tokenFor($owner, $accountId))) ->assertOk() - ->assertJsonPath('data.required_by_accounts', [$accountName]); + ->assertJsonPath('data.required_by_accounts', []) + ->assertJsonPath('data.required_by_sso_accounts', [$accountName]); } public function test_support_cannot_reset_two_factor_for_the_owner_of_an_account_that_requires_sso(): void @@ -282,6 +304,26 @@ public function test_forgot_password_sends_nothing_to_an_enforced_member(): void Mail::assertQueued(ForgotPassword::class, fn (ForgotPassword $mail) => $mail->hasTo('contractor@acme.test')); } + public function test_me_says_whether_a_password_can_be_set_so_enforced_members_are_not_offered_one(): void + { + [, $accountId, $connection] = $this->enforcingAccount(); + $this->member($accountId, 'grace@acme.test', enforced: true); + $exempt = $this->member($accountId, 'contractor@acme.test', enforced: false); + + $this->getJson('/users/me', $this->headersFor($exempt, $accountId)) + ->assertOk() + ->assertJsonPath('data.password_reset_allowed', true); + + $token = $this->exchangeOidcCode($this->startConnectionFlow($connection), $this->connectionClaims('okta-grace', 'grace@acme.test')) + ->json('data.token'); + $this->app['auth']->forgetGuards(); + $this->app['tymon.jwt']->unsetToken(); + + $this->getJson('/users/me', $this->bearer($token)) + ->assertOk() + ->assertJsonPath('data.password_reset_allowed', false); + } + public function test_an_enforced_member_cannot_move_their_email_off_the_verified_domains(): void { [, $accountId, $connection] = $this->enforcingAccount(); diff --git a/backend/tests/Feature/Http/ApiKeys/ApiKeyAuthenticationTest.php b/backend/tests/Feature/Http/ApiKeys/ApiKeyAuthenticationTest.php new file mode 100644 index 0000000000..42a018e3a7 --- /dev/null +++ b/backend/tests/Feature/Http/ApiKeys/ApiKeyAuthenticationTest.php @@ -0,0 +1,280 @@ +owner, $this->accountId] = $this->createAccountOwner(); + $this->organizerId = $this->createOrganizer($this->accountId); + } + + public function test_a_key_authenticates_as_its_owner_in_its_account(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + + $this->getJson('/users/me', $this->apiKeyHeaders($apiKey)) + ->assertOk() + ->assertJsonPath('data.id', $this->owner->id) + ->assertJsonPath('data.account_id', $this->accountId); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey)) + ->assertOk() + ->assertJsonPath('data.0.id', $this->organizerId); + + $this->assertNotNull(DB::table('personal_access_tokens')->value('last_used_at')); + } + + public function test_last_used_is_recorded_at_most_every_few_minutes(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + $recentlyUsed = now()->subMinute()->startOfSecond(); + DB::table('personal_access_tokens')->update(['last_used_at' => $recentlyUsed]); + + $this->getJson('/users/me', $this->apiKeyHeaders($apiKey))->assertOk(); + $this->assertEquals($recentlyUsed, Carbon::parse(DB::table('personal_access_tokens')->value('last_used_at'))); + + DB::table('personal_access_tokens')->update(['last_used_at' => now()->subMinutes(10)]); + $this->getJson('/users/me', $this->apiKeyHeaders($apiKey))->assertOk(); + $this->assertTrue(Carbon::parse(DB::table('personal_access_tokens')->value('last_used_at'))->gt(now()->subMinute())); + } + + public function test_the_numeric_id_prefix_form_is_accepted(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + $id = DB::table('personal_access_tokens')->value('id'); + + $this->getJson('/organizers', $this->apiKeyHeaders($id.'|'.$apiKey))->assertOk(); + } + + public function test_read_only_keys_cannot_write(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId, ApiKeyAccess::READ_ONLY); + + $this->getJson("/organizers/$this->organizerId", $this->apiKeyHeaders($apiKey))->assertOk(); + + $this->postJson('/organizers', $this->organizerPayload(), $this->apiKeyHeaders($apiKey)) + ->assertForbidden() + ->assertJsonPath('error_code', 'API_KEY_READ_ONLY'); + + $this->assertDatabaseMissing('organizers', ['name' => 'Created over the API']); + } + + public function test_read_write_keys_can_write(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + + $this->postJson('/organizers', $this->organizerPayload(), $this->apiKeyHeaders($apiKey))->assertCreated(); + + $this->assertDatabaseHas('organizers', ['name' => 'Created over the API', 'account_id' => $this->accountId]); + } + + public function test_a_key_never_exceeds_its_owners_role(): void + { + $viewer = $this->addAccountUser($this->accountId, Role::VIEWER); + $apiKey = $this->createApiKey($viewer, $this->accountId); + + $this->postJson('/organizers', $this->organizerPayload(), $this->apiKeyHeaders($apiKey)) + ->assertForbidden() + ->assertJsonPath('error_code', 'READ_ONLY_ROLE'); + } + + public function test_a_restricted_members_key_only_reaches_their_grants(): void + { + $otherOrganizerId = $this->createOrganizer($this->accountId, 'Not granted'); + $member = $this->addRestrictedAccountUser($this->accountId, organizerIds: [$this->organizerId]); + $apiKey = $this->createApiKey($member, $this->accountId); + + $this->getJson("/organizers/$this->organizerId", $this->apiKeyHeaders($apiKey))->assertOk(); + $this->getJson("/organizers/$otherOrganizerId", $this->apiKeyHeaders($apiKey))->assertForbidden(); + } + + public function test_a_key_is_scoped_to_its_account(): void + { + [$otherOwner, $otherAccountId] = $this->createAccountOwner(); + $otherOrganizerId = $this->createOrganizer($otherAccountId); + $this->owner->accounts()->attach($otherAccountId, [ + 'role' => Role::ADMIN->value, + 'status' => UserStatus::ACTIVE->name, + 'is_account_owner' => false, + ]); + + $apiKey = $this->createApiKey($this->owner, $this->accountId); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey)) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $this->organizerId); + + $this->getJson("/organizers/$otherOrganizerId", $this->apiKeyHeaders($apiKey))->assertForbidden(); + } + + public function test_expired_keys_are_rejected(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + DB::table('personal_access_tokens')->update(['expires_at' => now()->subMinute()]); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey)) + ->assertUnauthorized() + ->assertJsonPath('message', 'This API key is invalid, has expired or has been revoked.'); + } + + public function test_revoked_keys_are_rejected(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + DB::table('personal_access_tokens')->delete(); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey))->assertUnauthorized(); + } + + public function test_keys_stop_working_when_the_member_is_deactivated_or_removed(): void + { + $member = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $apiKey = $this->createApiKey($member, $this->accountId); + + DB::table('account_users')->where(['user_id' => $member->id, 'account_id' => $this->accountId])->update(['status' => UserStatus::INACTIVE->name]); + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey))->assertUnauthorized(); + + DB::table('account_users')->where(['user_id' => $member->id, 'account_id' => $this->accountId])->delete(); + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey))->assertUnauthorized(); + } + + public function test_keys_stop_working_when_the_user_is_deleted(): void + { + $member = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $apiKey = $this->createApiKey($member, $this->accountId); + + $member->delete(); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey))->assertUnauthorized(); + } + + public function test_revoking_a_users_sessions_also_revokes_their_keys(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + [$otherOwner, $otherAccountId] = $this->createAccountOwner(); + $otherApiKey = $this->createApiKey($otherOwner, $otherAccountId); + + app(UserSessionRevocationService::class)->revokeAll($this->owner->id); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey))->assertUnauthorized(); + $this->assertDatabaseMissing('personal_access_tokens', ['tokenable_id' => $this->owner->id]); + $this->getJson('/organizers', $this->apiKeyHeaders($otherApiKey))->assertOk(); + } + + public function test_a_password_reset_revokes_keys(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + $token = 'reset-token-'.uniqid(); + DB::table('password_reset_tokens')->insert([ + 'email' => $this->owner->email, + 'token' => $token, + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $this->postJson("/auth/reset-password/$token", [ + 'password' => 'a-brand-new-password-123', + 'password_confirmation' => 'a-brand-new-password-123', + ])->assertSuccessful(); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey))->assertUnauthorized(); + } + + public function test_session_only_routes_refuse_keys(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + + foreach ([ + ['PUT', '/users/me'], + ['GET', '/users'], + ['GET', '/users/me/two-factor'], + ['POST', '/accounts/deletion-request'], + ['PUT', "/accounts/$this->accountId"], + ['POST', "/organizers/$this->organizerId/stripe/connect"], + ['GET', '/auth/logout'], + ] as [$method, $uri]) { + $this->json($method, $uri, [], $this->apiKeyHeaders($apiKey)) + ->assertForbidden() + ->assertJsonPath('error_code', 'API_KEY_NOT_ALLOWED'); + } + } + + public function test_account_two_factor_requirement_applies_to_keys(): void + { + $member = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $apiKey = $this->createApiKey($member, $this->accountId); + DB::table('accounts')->where('id', $this->accountId)->update(['require_two_factor_authentication' => true]); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey)) + ->assertForbidden() + ->assertJsonPath('error_code', 'TWO_FACTOR_SETUP_REQUIRED'); + } + + public function test_keys_are_blocked_while_the_account_is_pending_deletion(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + DB::table('account_deletion_requests')->insert([ + 'account_id' => $this->accountId, + 'requested_by_user_id' => $this->owner->id, + 'initiated_by' => 'USER', + 'status' => 'REQUESTED', + 'expected_outcome' => 'HARD_DELETE', + 'scheduled_deletion_at' => now()->addDays(30), + 'created_at' => now(), + 'updated_at' => now(), + ]); + Cache::flush(); + + $this->getJson('/organizers', $this->apiKeyHeaders($apiKey)) + ->assertForbidden() + ->assertJsonPath('error_code', 'ACCOUNT_PENDING_DELETION'); + } + + public function test_keys_on_public_routes_are_ignored(): void + { + $this->getJson('/public/instance', $this->apiKeyHeaders('hie_not-a-real-key'))->assertOk(); + } + + public function test_a_session_token_still_works_on_every_route_group(): void + { + $this->getJson('/organizers', $this->headersFor($this->owner, $this->accountId))->assertOk(); + $this->getJson('/users', $this->headersFor($this->owner, $this->accountId))->assertOk(); + } + + private function organizerPayload(): array + { + return [ + 'name' => 'Created over the API', + 'email' => 'api@example.com', + 'currency' => 'USD', + 'timezone' => 'UTC', + ]; + } +} diff --git a/backend/tests/Feature/Http/ApiKeys/ApiKeyManagementTest.php b/backend/tests/Feature/Http/ApiKeys/ApiKeyManagementTest.php new file mode 100644 index 0000000000..32ee328904 --- /dev/null +++ b/backend/tests/Feature/Http/ApiKeys/ApiKeyManagementTest.php @@ -0,0 +1,286 @@ +owner = User::factory()->password(self::PASSWORD)->withAccount()->create(); + $this->accountId = $this->owner->accounts()->first()->id; + } + + public function test_member_creates_a_key_and_sees_it_once(): void + { + $response = $this->postJson('/api-keys', $this->payload(), $this->headersFor($this->owner, $this->accountId)) + ->assertCreated() + ->assertJsonPath('data.name', 'Zapier production') + ->assertJsonPath('data.access', ApiKeyAccess::READ_WRITE->value) + ->assertJsonPath('data.is_own', true) + ->assertJsonPath('data.is_expired', false) + ->assertJsonPath('data.last_used_at', null) + ->assertJsonPath('data.owner.id', $this->owner->id); + + $key = $response->json('data.key'); + $this->assertStringStartsWith('hie_', $key); + $this->assertSame(substr($key, -4), $response->json('data.token_hint')); + + $row = DB::table('personal_access_tokens')->where('id', $response->json('data.id'))->first(); + $this->assertSame(hash('sha256', $key), $row->token); + $this->assertSame($this->accountId, $row->account_id); + $this->assertSame($this->owner->id, $row->tokenable_id); + $this->assertSame(User::class, $row->tokenable_type); + $this->assertSame(['read', 'write'], json_decode($row->abilities, true)); + $this->assertTrue(CarbonImmutable::parse($row->expires_at)->isSameDay(now()->addDays(90))); + + Mail::assertQueued(ApiKeyCreatedMail::class, fn (ApiKeyCreatedMail $mail) => $mail->hasTo($this->owner->email)); + + $this->getJson('/api-keys', $this->headersFor($this->owner, $this->accountId)) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonMissingPath('data.0.key'); + } + + public function test_key_without_expiry_never_expires(): void + { + $response = $this->postJson('/api-keys', $this->payload(['expires_in_days' => null]), $this->headersFor($this->owner, $this->accountId)) + ->assertCreated() + ->assertJsonPath('data.expires_at', null); + + $this->assertNull(DB::table('personal_access_tokens')->where('id', $response->json('data.id'))->value('expires_at')); + } + + public function test_creating_a_key_needs_the_password(): void + { + $this->postJson('/api-keys', $this->payload(['password' => 'wrong-password']), $this->headersFor($this->owner, $this->accountId)) + ->assertUnprocessable() + ->assertJsonValidationErrors('password'); + + $this->postJson('/api-keys', $this->payload(['password' => null]), $this->headersFor($this->owner, $this->accountId)) + ->assertUnprocessable() + ->assertJsonValidationErrors('password'); + + $this->assertDatabaseCount('personal_access_tokens', 0); + Mail::assertNothingQueued(); + } + + public function test_validation_rejects_bad_input(): void + { + $headers = $this->headersFor($this->owner, $this->accountId); + + $this->postJson('/api-keys', $this->payload(['name' => '[Click here](https://example.com)']), $headers) + ->assertUnprocessable() + ->assertJsonValidationErrors('name'); + + $this->postJson('/api-keys', $this->payload(['name' => str_repeat('a', 61)]), $headers) + ->assertUnprocessable() + ->assertJsonValidationErrors('name'); + + $this->postJson('/api-keys', $this->payload(['expires_in_days' => 13]), $headers) + ->assertUnprocessable() + ->assertJsonValidationErrors('expires_in_days'); + + $this->postJson('/api-keys', $this->payload(['access' => 'ADMIN']), $headers) + ->assertUnprocessable() + ->assertJsonValidationErrors('access'); + + $this->assertDatabaseCount('personal_access_tokens', 0); + } + + public function test_keys_cannot_be_created_while_impersonating(): void + { + [$admin] = $this->createAccountOwner(); + $this->app['auth']->forgetGuards(); + $this->app['tymon.jwt']->unsetToken(); + + $token = JWTAuth::claims([ + 'account_id' => $this->accountId, + 'impersonator_id' => $admin->id, + 'is_impersonating' => true, + ])->fromUser($this->owner); + + $this->postJson('/api-keys', $this->payload(), ['Authorization' => 'Bearer '.$token]) + ->assertForbidden() + ->assertJsonPath('error_code', 'IMPERSONATION_NOT_ALLOWED'); + + $this->assertDatabaseCount('personal_access_tokens', 0); + } + + public function test_unverified_users_cannot_create_keys(): void + { + $this->owner->forceFill(['email_verified_at' => null])->save(); + + $this->postJson('/api-keys', $this->payload(), $this->headersFor($this->owner, $this->accountId)) + ->assertConflict(); + + $this->assertDatabaseCount('personal_access_tokens', 0); + } + + public function test_viewers_can_only_create_read_only_keys(): void + { + $viewer = $this->addAccountUser($this->accountId, Role::VIEWER); + $viewer->forceFill(['password' => bcrypt(self::PASSWORD)])->save(); + + $this->postJson('/api-keys', $this->payload(), $this->headersFor($viewer, $this->accountId)) + ->assertForbidden() + ->assertJsonPath('error_code', 'PERMISSION_REQUIRED'); + + $this->postJson('/api-keys', $this->payload(['access' => ApiKeyAccess::READ_ONLY->value]), $this->headersFor($viewer, $this->accountId)) + ->assertCreated() + ->assertJsonPath('data.access', ApiKeyAccess::READ_ONLY->value); + } + + public function test_members_have_an_active_key_limit(): void + { + for ($i = 0; $i < ApiKeyService::MAX_ACTIVE_KEYS_PER_MEMBER; $i++) { + $this->createApiKey($this->owner, $this->accountId); + } + + $this->postJson('/api-keys', $this->payload(), $this->headersFor($this->owner, $this->accountId)) + ->assertConflict(); + + DB::table('personal_access_tokens')->limit(1)->update(['expires_at' => now()->subMinute()]); + + $this->postJson('/api-keys', $this->payload(), $this->headersFor($this->owner, $this->accountId)) + ->assertCreated(); + } + + public function test_members_only_see_their_own_keys_and_admins_see_everyone(): void + { + $organizer = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $this->createApiKey($this->owner, $this->accountId, name: 'Owner key'); + $this->createApiKey($organizer, $this->accountId, name: 'Organizer key'); + + [$otherOwner, $otherAccountId] = $this->createAccountOwner(); + $this->createApiKey($otherOwner, $otherAccountId, name: 'Other account key'); + + $this->getJson('/api-keys', $this->headersFor($organizer, $this->accountId)) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.name', 'Organizer key') + ->assertJsonPath('data.0.is_own', true); + + $this->getJson('/api-keys', $this->headersFor($this->owner, $this->accountId)) + ->assertOk() + ->assertJsonCount(2, 'data') + ->assertJsonPath('data.0.name', 'Organizer key') + ->assertJsonPath('data.0.is_own', false) + ->assertJsonPath('data.0.owner.email', $organizer->email) + ->assertJsonPath('data.1.name', 'Owner key') + ->assertJsonPath('data.1.is_own', true); + } + + public function test_members_revoke_their_own_keys(): void + { + $organizer = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $this->createApiKey($organizer, $this->accountId); + $keyId = DB::table('personal_access_tokens')->value('id'); + + $this->deleteJson("/api-keys/$keyId", [], $this->headersFor($organizer, $this->accountId))->assertNoContent(); + + $this->assertDatabaseMissing('personal_access_tokens', ['id' => $keyId]); + Mail::assertNotQueued(ApiKeyRevokedMail::class); + } + + public function test_members_cannot_revoke_someone_elses_key(): void + { + $organizer = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $this->createApiKey($this->owner, $this->accountId); + $keyId = DB::table('personal_access_tokens')->value('id'); + + $this->deleteJson("/api-keys/$keyId", [], $this->headersFor($organizer, $this->accountId))->assertNotFound(); + + $this->assertDatabaseHas('personal_access_tokens', ['id' => $keyId]); + } + + public function test_admins_revoke_a_members_key_and_the_member_is_told(): void + { + $organizer = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $this->createApiKey($organizer, $this->accountId, name: 'Forgotten integration'); + $keyId = DB::table('personal_access_tokens')->value('id'); + + $this->deleteJson("/api-keys/$keyId", [], $this->headersFor($this->owner, $this->accountId))->assertNoContent(); + + $this->assertDatabaseMissing('personal_access_tokens', ['id' => $keyId]); + Mail::assertQueued(ApiKeyRevokedMail::class, fn (ApiKeyRevokedMail $mail) => $mail->hasTo($organizer->email)); + } + + public function test_keys_from_another_account_are_not_found(): void + { + [$otherOwner, $otherAccountId] = $this->createAccountOwner(); + $this->createApiKey($otherOwner, $otherAccountId); + $keyId = DB::table('personal_access_tokens')->value('id'); + + $this->deleteJson("/api-keys/$keyId", [], $this->headersFor($this->owner, $this->accountId))->assertNotFound(); + + $this->assertDatabaseHas('personal_access_tokens', ['id' => $keyId]); + } + + public function test_api_keys_cannot_manage_api_keys(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + + $this->getJson('/api-keys', $this->apiKeyHeaders($apiKey)) + ->assertForbidden() + ->assertJsonPath('error_code', 'API_KEY_NOT_ALLOWED'); + + $this->postJson('/api-keys', $this->payload(), $this->apiKeyHeaders($apiKey)) + ->assertForbidden() + ->assertJsonPath('error_code', 'API_KEY_NOT_ALLOWED'); + } + + public function test_deactivating_a_member_revokes_their_keys(): void + { + $organizer = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $this->createApiKey($organizer, $this->accountId); + + $this->putJson("/users/$organizer->id", [ + 'first_name' => 'Gone', + 'last_name' => 'Member', + 'role' => Role::ORGANIZER->value, + 'status' => 'INACTIVE', + ], $this->headersFor($this->owner, $this->accountId))->assertOk(); + + $this->assertDatabaseMissing('personal_access_tokens', ['tokenable_id' => $organizer->id]); + } + + private function payload(array $overrides = []): array + { + return [ + 'name' => 'Zapier production', + 'access' => ApiKeyAccess::READ_WRITE->value, + 'expires_in_days' => 90, + 'password' => self::PASSWORD, + ...$overrides, + ]; + } +} diff --git a/backend/tests/Feature/Http/ApiKeys/ApiKeyRouteExposureTest.php b/backend/tests/Feature/Http/ApiKeys/ApiKeyRouteExposureTest.php new file mode 100644 index 0000000000..5782a8084a --- /dev/null +++ b/backend/tests/Feature/Http/ApiKeys/ApiKeyRouteExposureTest.php @@ -0,0 +1,158 @@ +authenticatedRoutes()) + ->filter(fn (Route $route) => $this->authGuards($route) === self::SESSION_ONLY_GUARD) + ->reject(fn (Route $route) => str_starts_with($route->uri(), 'admin/')) + ->map(fn (Route $route) => $this->describe($route)) + ->sort() + ->values() + ->all(); + + $this->assertSame(self::SESSION_ONLY_ROUTES, $sessionOnly); + } + + public function test_admin_routes_never_accept_keys(): void + { + foreach ($this->authenticatedRoutes() as $route) { + if (str_starts_with($route->uri(), 'admin/')) { + $this->assertSame(self::SESSION_ONLY_GUARD, $this->authGuards($route), $this->describe($route)); + } + } + } + + public function test_every_signed_in_route_uses_a_known_guard(): void + { + foreach ($this->authenticatedRoutes() as $route) { + $this->assertContains($this->authGuards($route), [self::SESSION_ONLY_GUARD, self::API_KEY_GUARDS], $this->describe($route)); + } + } + + public function test_team_account_and_payment_settings_are_never_reachable_with_a_key(): void + { + foreach ($this->authenticatedRoutes() as $route) { + $controller = $route->getControllerClass(); + + if ($controller === null || $this->authGuards($route) !== self::API_KEY_GUARDS) { + continue; + } + + $required = collect((new ReflectionClass($controller))->getAttributes(RequiresPermission::class)) + ->flatMap(fn ($attribute) => $attribute->newInstance()->permissions) + ->all(); + + $blocked = array_filter($required, static fn (Permission $permission) => in_array($permission, self::PERMISSIONS_KEYS_CANNOT_USE, true)); + + $this->assertSame([], array_values($blocked), $this->describe($route)); + } + } + + /** + * @return Route[] + */ + private function authenticatedRoutes(): array + { + return array_values(array_filter( + RouteFacade::getRoutes()->getRoutes(), + fn (Route $route) => $this->authGuards($route) !== null, + )); + } + + private function authGuards(Route $route): ?string + { + return collect($route->gatherMiddleware()) + ->first(static fn ($middleware) => is_string($middleware) && str_starts_with($middleware, 'auth:')); + } + + private function describe(Route $route): string + { + return implode('|', array_diff($route->methods(), ['HEAD'])).' '.$route->uri(); + } +} diff --git a/backend/tests/Feature/Http/TeamAccess/AccessCoverageTest.php b/backend/tests/Feature/Http/TeamAccess/AccessCoverageTest.php index b3e4d72bf8..97919f0871 100644 --- a/backend/tests/Feature/Http/TeamAccess/AccessCoverageTest.php +++ b/backend/tests/Feature/Http/TeamAccess/AccessCoverageTest.php @@ -12,6 +12,10 @@ use HiEvents\Http\Actions\Affiliates\ExportAffiliatesAction; use HiEvents\Http\Actions\Announcements\DismissAnnouncementAction; use HiEvents\Http\Actions\Announcements\GetActiveAnnouncementsAction; +use HiEvents\Http\Actions\ApiKeys\CreateApiKeyAction; +use HiEvents\Http\Actions\ApiKeys\DeleteApiKeyAction; +use HiEvents\Http\Actions\ApiKeys\GetApiKeysAction; +use HiEvents\Http\Actions\ApiReference\GetApiReferenceAction; use HiEvents\Http\Actions\Attendees\ExportAttendeesAction; use HiEvents\Http\Actions\Auth\JoinInvitationAction; use HiEvents\Http\Actions\Auth\LogoutAction; @@ -110,6 +114,10 @@ class AccessCoverageTest extends TestCase ExchangeAuthenticatedSsoCodeAction::class, DeleteSsoIdentityAction::class, JoinInvitationAction::class, + GetApiKeysAction::class, + CreateApiKeyAction::class, + DeleteApiKeyAction::class, + GetApiReferenceAction::class, ]; private const SCOPE_FILTERED_OR_CHECKED_INLINE = [ @@ -135,6 +143,8 @@ class AccessCoverageTest extends TestCase private const ALLOWED_FOR_READ_ONLY_USERS = [ BeginTwoFactorSetupAction::class, + CreateApiKeyAction::class, + DeleteApiKeyAction::class, DeleteSsoIdentityAction::class, ExchangeAuthenticatedSsoCodeAction::class, GetSsoIdentitiesAction::class, @@ -236,7 +246,7 @@ public function test_account_level_actions_require_their_permission(): void private function loggedInActions(): array { return collect(app('router')->getRoutes()->getRoutes()) - ->filter(fn (Route $route) => in_array('auth:api', $route->gatherMiddleware(), true)) + ->filter(fn (Route $route) => array_intersect(['auth:api', 'auth:api,sanctum'], $route->gatherMiddleware()) !== []) ->reject(fn (Route $route) => str_starts_with($route->uri(), 'admin/')) ->map(fn (Route $route) => $route->getControllerClass()) ->filter() diff --git a/backend/tests/Feature/Http/TeamAccess/TagManagerAccessTest.php b/backend/tests/Feature/Http/TeamAccess/TagManagerAccessTest.php index 76f345d4bd..5efb4f56bd 100644 --- a/backend/tests/Feature/Http/TeamAccess/TagManagerAccessTest.php +++ b/backend/tests/Feature/Http/TeamAccess/TagManagerAccessTest.php @@ -6,12 +6,14 @@ use HiEvents\Models\User; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\Concerns\ActsAsAccountUsers; +use Tests\Concerns\UsesApiKeys; use Tests\TestCase; class TagManagerAccessTest extends TestCase { use ActsAsAccountUsers; use DatabaseTransactions; + use UsesApiKeys; private User $owner; @@ -48,6 +50,21 @@ public function test_only_account_admins_can_add_a_tag_manager_container(): void ->assertOk(); } + public function test_an_api_key_cannot_add_a_tag_manager_container_even_for_an_admin(): void + { + $apiKey = $this->createApiKey($this->owner, $this->accountId); + + $this->patchJson("/organizers/{$this->organizerId}/settings", $this->tagManager('GTM-VIAKEY1'), $this->apiKeyHeaders($apiKey)) + ->assertUnprocessable() + ->assertJsonValidationErrors('tracking_pixels.0.provider'); + + $this->patchJson("/organizers/{$this->organizerId}/settings", $this->tagManager('GTM-OWNER1'), $this->headersFor($this->owner, $this->accountId)) + ->assertOk(); + + $this->patchJson("/organizers/{$this->organizerId}/settings", $this->tagManager('GTM-OWNER1'), $this->apiKeyHeaders($apiKey)) + ->assertOk(); + } + private function tagManager(string $containerId): array { return [ diff --git a/backend/tests/Feature/OpenApi/IntegrationApiReferenceTest.php b/backend/tests/Feature/OpenApi/IntegrationApiReferenceTest.php new file mode 100644 index 0000000000..22e0ff2a06 --- /dev/null +++ b/backend/tests/Feature/OpenApi/IntegrationApiReferenceTest.php @@ -0,0 +1,64 @@ +timeout(180) + ->run([PHP_BINARY, '-d', 'memory_limit=1G', 'artisan', 'api-reference:export', '--check']); + + $this->assertTrue($result->successful(), trim($result->output().$result->errorOutput())); + } + + public function test_signed_in_users_get_the_reference_for_api_key_endpoints_only(): void + { + [$owner, $accountId] = $this->createAccountOwner(); + + $response = $this->getJson('/api-reference', $this->headersFor($owner, $accountId))->assertOk(); + + $paths = $response->json('paths'); + $this->assertArrayHasKey('/events', $paths); + $this->assertArrayHasKey('/users/me', $paths); + $this->assertSame(['get'], array_keys($paths['/users/me'])); + $this->assertArrayNotHasKey('/api-keys', $paths); + $this->assertArrayNotHasKey('/users', $paths); + $this->assertSame([], array_values(array_filter(array_keys($paths), static fn (string $path) => str_starts_with($path, '/auth/')))); + $this->assertArrayHasKey('/public/events/{eventId}', $paths); + $this->assertArrayNotHasKey('/public/instance', $paths); + $this->assertArrayNotHasKey('/public/compliance', $paths); + $this->assertArrayNotHasKey('/public/webhooks/stripe', $paths); + + $schemas = $response->json('components.schemas'); + $this->assertArrayNotHasKey('LoginRequest', $schemas); + $this->assertArrayNotHasKey('CreatedApiKeyResource', $schemas); + $this->assertArrayNotHasKey('UpsertAccountSsoConnectionRequest', $schemas); + $this->assertArrayHasKey('EventResource', $schemas); + $this->assertArrayNotHasKey('servers', $response->json()); + $this->assertSame(trim(file_get_contents(base_path('VERSION'))), $response->json('info.version')); + } + + public function test_the_reference_needs_a_signed_in_session(): void + { + [$owner, $accountId] = $this->createAccountOwner(); + $apiKey = $this->createApiKey($owner, $accountId, ApiKeyAccess::READ_ONLY); + + $this->getJson('/api-reference')->assertUnauthorized(); + $this->getJson('/api-reference', $this->apiKeyHeaders($apiKey)) + ->assertForbidden() + ->assertJsonPath('error_code', 'API_KEY_NOT_ALLOWED'); + } +} diff --git a/backend/tests/Feature/OpenApi/OpenApiGenerationTest.php b/backend/tests/Feature/OpenApi/OpenApiGenerationTest.php index bbc656773d..645a781876 100644 --- a/backend/tests/Feature/OpenApi/OpenApiGenerationTest.php +++ b/backend/tests/Feature/OpenApi/OpenApiGenerationTest.php @@ -22,7 +22,8 @@ public function test_openapi_document_generates_with_expected_paths(): void trim(file_get_contents(base_path('VERSION'))), $document['info']['version'], ); - $this->assertStringContainsString('unversioned', $document['info']['description']); + $this->assertStringContainsString('backward compatible', $document['info']['description']); + $this->assertStringContainsString('API_KEY_NOT_ALLOWED', $document['info']['description']); $this->assertArrayHasKey('/auth/login', $document['paths']); $this->assertArrayHasKey('/events', $document['paths']); @@ -48,10 +49,17 @@ public function test_openapi_document_generates_with_expected_paths(): void $this->assertSame([], $document['paths']['/auth/login']['post']['security']); $this->assertSame( - ['type' => 'http', 'scheme' => 'bearer', 'bearerFormat' => 'JWT'], + ['type' => 'http', 'description' => 'An API key (`hie_…`) or a session token from `POST /auth/login`.', 'scheme' => 'bearer'], $document['components']['securitySchemes']['http'], ); + $this->assertArrayHasKey('/api-keys', $document['paths']); + $this->assertArrayNotHasKey('/public/instance', $document['paths']); + $this->assertArrayNotHasKey('/public/compliance', $document['paths']); + $this->assertStringContainsString('Not available to API keys', $document['paths']['/api-keys']['post']['description']); + $this->assertStringContainsString('Not available to API keys', $document['paths']['/users']['get']['description']); + $this->assertStringNotContainsString('Not available to API keys', $document['paths']['/events']['get']['description'] ?? ''); + $eventsListSchema = $document['paths']['/events']['get']['responses']['200']['content']['application/json']['schema']; $this->assertSame( ['data', 'links', 'meta'], diff --git a/backend/tests/Feature/Services/Domain/Account/AccountDeletionExecutionTest.php b/backend/tests/Feature/Services/Domain/Account/AccountDeletionExecutionTest.php index d59c34bf1e..095f745068 100644 --- a/backend/tests/Feature/Services/Domain/Account/AccountDeletionExecutionTest.php +++ b/backend/tests/Feature/Services/Domain/Account/AccountDeletionExecutionTest.php @@ -166,6 +166,36 @@ public function test_both_deletion_paths_remove_the_sso_connection_domains_and_c } } + public function test_both_deletion_paths_remove_the_accounts_api_keys_and_keep_other_accounts_keys(): void + { + foreach ([AccountAnonymizationService::class => 'anonymizeAccount', AccountHardDeletionService::class => 'deleteAccount'] as $service => $method) { + $doomedKeyId = $this->seedApiKey($this->sharedUser, $this->account->id); + $survivingKeyId = $this->seedApiKey($this->sharedUser, $this->otherAccount->id); + + $this->app->make($service)->{$method}($this->account->id); + + $this->assertDatabaseMissing('personal_access_tokens', ['id' => $doomedKeyId]); + $this->assertDatabaseHas('personal_access_tokens', ['id' => $survivingKeyId]); + + DB::table('personal_access_tokens')->delete(); + } + } + + private function seedApiKey(User $user, int $accountId): int + { + return DB::table('personal_access_tokens')->insertGetId([ + 'tokenable_type' => User::class, + 'tokenable_id' => $user->id, + 'account_id' => $accountId, + 'name' => 'Integration', + 'token' => hash('sha256', uniqid('', true)), + 'token_hint' => 'abcd', + 'abilities' => '["read"]', + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + private function seedSsoConnection(): int { $connectionId = DB::table('account_sso_connections')->insertGetId([ diff --git a/backend/tests/Feature/Services/Domain/Auth/ApiKey/ApiKeyExpiryReminderServiceTest.php b/backend/tests/Feature/Services/Domain/Auth/ApiKey/ApiKeyExpiryReminderServiceTest.php new file mode 100644 index 0000000000..7f66aeec7f --- /dev/null +++ b/backend/tests/Feature/Services/Domain/Auth/ApiKey/ApiKeyExpiryReminderServiceTest.php @@ -0,0 +1,89 @@ +owner, $this->accountId] = $this->createAccountOwner(); + } + + public function test_reminds_the_owner_once_when_a_key_is_about_to_expire(): void + { + $keyId = $this->keyCreatedDaysAgo($this->owner, createdDaysAgo: 85, expiresInDays: 5); + + Mail::fake(); + (new SendApiKeyExpiryRemindersJob)->handle(app(ApiKeyExpiryReminderService::class)); + + Mail::assertQueued(ApiKeyExpiringMail::class, fn (ApiKeyExpiringMail $mail) => $mail->hasTo($this->owner->email)); + $this->assertNotNull(DB::table('personal_access_tokens')->where('id', $keyId)->value('expiry_reminder_sent_at')); + + Mail::fake(); + (new SendApiKeyExpiryRemindersJob)->handle(app(ApiKeyExpiryReminderService::class)); + + Mail::assertNothingQueued(); + } + + public function test_skips_keys_that_are_not_due_short_lived_or_never_expire(): void + { + $this->keyCreatedDaysAgo($this->owner, createdDaysAgo: 30, expiresInDays: 60); + $this->keyCreatedDaysAgo($this->owner, createdDaysAgo: 2, expiresInDays: 5); + $this->keyCreatedDaysAgo($this->owner, createdDaysAgo: 100, expiresInDays: -1); + $this->keyCreatedDaysAgo($this->owner, createdDaysAgo: 100, expiresInDays: null); + + Mail::fake(); + (new SendApiKeyExpiryRemindersJob)->handle(app(ApiKeyExpiryReminderService::class)); + + Mail::assertNothingQueued(); + } + + public function test_does_not_email_members_who_lost_access(): void + { + $member = $this->addAccountUser($this->accountId, Role::ORGANIZER); + $keyId = $this->keyCreatedDaysAgo($member, createdDaysAgo: 85, expiresInDays: 5); + DB::table('account_users')->where(['user_id' => $member->id])->update(['status' => UserStatus::INACTIVE->name]); + + Mail::fake(); + (new SendApiKeyExpiryRemindersJob)->handle(app(ApiKeyExpiryReminderService::class)); + + Mail::assertNothingQueued(); + $this->assertNotNull(DB::table('personal_access_tokens')->where('id', $keyId)->value('expiry_reminder_sent_at')); + } + + private function keyCreatedDaysAgo(User $user, int $createdDaysAgo, ?int $expiresInDays): int + { + $this->createApiKey($user, $this->accountId); + $keyId = DB::table('personal_access_tokens')->max('id'); + + DB::table('personal_access_tokens')->where('id', $keyId)->update([ + 'created_at' => now()->subDays($createdDaysAgo), + 'expires_at' => $expiresInDays === null ? null : now()->addDays($expiresInDays), + ]); + + return $keyId; + } +} diff --git a/backend/tests/Unit/DomainObjects/Enums/ApiKeyAccessTest.php b/backend/tests/Unit/DomainObjects/Enums/ApiKeyAccessTest.php new file mode 100644 index 0000000000..9bb7ea1595 --- /dev/null +++ b/backend/tests/Unit/DomainObjects/Enums/ApiKeyAccessTest.php @@ -0,0 +1,19 @@ +assertSame(['read'], ApiKeyAccess::READ_ONLY->abilities()); + $this->assertSame(['read', 'write'], ApiKeyAccess::READ_WRITE->abilities()); + + $this->assertSame(ApiKeyAccess::READ_ONLY, ApiKeyAccess::fromAbilities(['read'])); + $this->assertSame(ApiKeyAccess::READ_WRITE, ApiKeyAccess::fromAbilities(['read', 'write'])); + $this->assertSame(ApiKeyAccess::READ_ONLY, ApiKeyAccess::fromAbilities([])); + } +} diff --git a/backend/tests/Unit/Services/Application/Handlers/ApiKey/CreateApiKeyHandlerTest.php b/backend/tests/Unit/Services/Application/Handlers/ApiKey/CreateApiKeyHandlerTest.php new file mode 100644 index 0000000000..dbbb5637eb --- /dev/null +++ b/backend/tests/Unit/Services/Application/Handlers/ApiKey/CreateApiKeyHandlerTest.php @@ -0,0 +1,143 @@ +userRepository = m::mock(UserRepositoryInterface::class); + $this->identityConfirmationService = m::mock(IdentityConfirmationService::class); + $this->apiKeyService = m::mock(ApiKeyService::class); + + $this->handler = new CreateApiKeyHandler($this->userRepository, $this->identityConfirmationService, $this->apiKeyService); + } + + public function test_creates_the_key_after_confirming_identity(): void + { + $user = $this->user(); + $created = new CreatedApiKeyDTO(apiKey: new PersonalAccessTokenDomainObject, plainTextKey: 'hie_key'); + + $this->apiKeyService->shouldReceive('countActiveKeys')->with(self::USER_ID, self::ACCOUNT_ID)->andReturn(0); + $this->identityConfirmationService->shouldReceive('confirm')->once()->with($user, 'secret', null, 'jti-1'); + $this->apiKeyService->shouldReceive('create')->once()->with($user, self::ACCOUNT_ID, 'Zapier', ApiKeyAccess::READ_WRITE, 30)->andReturn($created); + + $this->assertSame($created, $this->handler->handle($this->dto())); + } + + public function test_unverified_users_are_refused_before_anything_else(): void + { + $this->user(verified: false); + + $this->identityConfirmationService->shouldNotReceive('confirm'); + $this->apiKeyService->shouldNotReceive('create'); + + $this->expectException(ResourceConflictException::class); + + $this->handler->handle($this->dto()); + } + + public function test_members_without_edit_cannot_create_write_keys(): void + { + $this->user(); + + $this->apiKeyService->shouldNotReceive('create'); + + $this->expectException(PermissionRequiredException::class); + + $this->handler->handle($this->dto(canEdit: false)); + } + + public function test_members_without_edit_can_create_read_only_keys(): void + { + $user = $this->user(); + + $this->apiKeyService->shouldReceive('countActiveKeys')->andReturn(0); + $this->identityConfirmationService->shouldReceive('confirm')->once(); + $this->apiKeyService->shouldReceive('create')->once()->with($user, self::ACCOUNT_ID, 'Zapier', ApiKeyAccess::READ_ONLY, 30) + ->andReturn(new CreatedApiKeyDTO(apiKey: new PersonalAccessTokenDomainObject, plainTextKey: 'hie_key')); + + $this->handler->handle($this->dto(access: ApiKeyAccess::READ_ONLY, canEdit: false)); + } + + public function test_the_active_key_limit_is_enforced(): void + { + $this->user(); + + $this->apiKeyService->shouldReceive('countActiveKeys')->andReturn(ApiKeyService::MAX_ACTIVE_KEYS_PER_MEMBER); + $this->apiKeyService->shouldNotReceive('create'); + + $this->expectException(ResourceConflictException::class); + + $this->handler->handle($this->dto()); + } + + public function test_a_failed_identity_check_creates_nothing(): void + { + $this->user(); + + $this->apiKeyService->shouldReceive('countActiveKeys')->andReturn(0); + $this->identityConfirmationService->shouldReceive('confirm')->andThrow(new IdentityConfirmationFailedException('The password is incorrect.')); + $this->apiKeyService->shouldNotReceive('create'); + + $this->expectException(IdentityConfirmationFailedException::class); + + $this->handler->handle($this->dto()); + } + + private function user(bool $verified = true): UserDomainObject + { + $user = (new UserDomainObject) + ->setId(self::USER_ID) + ->setEmail('owner@example.com') + ->setEmailVerifiedAt($verified ? '2026-10-01 10:00:00' : null); + + $this->userRepository->shouldReceive('findById')->with(self::USER_ID)->andReturn($user); + + return $user; + } + + private function dto(ApiKeyAccess $access = ApiKeyAccess::READ_WRITE, bool $canEdit = true): CreateApiKeyDTO + { + return new CreateApiKeyDTO( + userId: self::USER_ID, + accountId: self::ACCOUNT_ID, + name: 'Zapier', + access: $access, + expiresInDays: 30, + canEdit: $canEdit, + password: 'secret', + tokenId: 'jti-1', + ); + } +} diff --git a/backend/tests/Unit/Services/Domain/Auth/AuthUserServiceTest.php b/backend/tests/Unit/Services/Domain/Auth/AuthUserServiceTest.php new file mode 100644 index 0000000000..4905622478 --- /dev/null +++ b/backend/tests/Unit/Services/Domain/Auth/AuthUserServiceTest.php @@ -0,0 +1,91 @@ +authManager = m::mock(AuthManager::class); + $this->authManager->shouldReceive('check')->andReturnTrue(); + + $this->service = new AuthUserService($this->authManager, m::mock(AccountUserRepositoryInterface::class)); + } + + public function test_api_key_requests_read_their_context_from_the_key(): void + { + $this->actingWithApiKey(id: 42, accountId: 9, abilities: ['read']); + $this->authManager->shouldNotReceive('payload'); + + $this->assertTrue($this->service->isApiKeyRequest()); + $this->assertSame(9, $this->service->getAuthenticatedAccountId()); + $this->assertSame('api-key:42', $this->service->getAuthenticatedTokenKey()); + $this->assertFalse($this->service->apiKeyCanWrite()); + $this->assertFalse($this->service->isImpersonating()); + $this->assertNull($this->service->getImpersonatorId()); + $this->assertNull($this->service->getSessionTokenId()); + } + + public function test_write_keys_can_write(): void + { + $this->actingWithApiKey(id: 1, accountId: 9, abilities: ['read', 'write']); + + $this->assertTrue($this->service->apiKeyCanWrite()); + } + + public function test_session_requests_read_their_context_from_the_jwt(): void + { + $this->authManager->shouldReceive('user')->andReturn(new User); + + $payload = m::mock(Payload::class); + $payload->shouldReceive('get')->with('account_id')->andReturn(5); + $payload->shouldReceive('get')->with('is_impersonating')->andReturn(true); + $payload->shouldReceive('get')->with('impersonator_id')->andReturn(11); + $payload->shouldReceive('get')->with('jti')->andReturn('jti-abc'); + $this->authManager->shouldReceive('payload')->andReturn($payload); + + $this->assertFalse($this->service->isApiKeyRequest()); + $this->assertTrue($this->service->apiKeyCanWrite()); + $this->assertSame(5, $this->service->getAuthenticatedAccountId()); + $this->assertTrue($this->service->isImpersonating()); + $this->assertSame(11, $this->service->getImpersonatorId()); + $this->assertSame('jti-abc', $this->service->getSessionTokenId()); + } + + public function test_logout_only_ends_sessions(): void + { + $this->actingWithApiKey(id: 1, accountId: 9, abilities: ['read']); + $this->authManager->shouldNotReceive('logout'); + + $this->service->logout(); + + $this->addToAssertionCount(1); + } + + /** + * @param string[] $abilities + */ + private function actingWithApiKey(int $id, int $accountId, array $abilities): void + { + $apiKey = new PersonalAccessToken; + $apiKey->forceFill(['id' => $id, 'account_id' => $accountId, 'abilities' => $abilities]); + + $this->authManager->shouldReceive('user')->andReturn((new User)->withAccessToken($apiKey)); + } +} diff --git a/backend/tests/Unit/Services/Domain/Auth/Sso/Enterprise/SsoSignupCaptureTest.php b/backend/tests/Unit/Services/Domain/Auth/Sso/Enterprise/SsoSignupCaptureTest.php new file mode 100644 index 0000000000..941952bf71 --- /dev/null +++ b/backend/tests/Unit/Services/Domain/Auth/Sso/Enterprise/SsoSignupCaptureTest.php @@ -0,0 +1,98 @@ +domainService = Mockery::mock(AccountSsoDomainService::class); + $this->connectionRepository = Mockery::mock(AccountSsoConnectionRepositoryInterface::class); + + $this->service = new SsoEnforcementService( + $this->connectionRepository, + Mockery::mock(AccountUserRepositoryInterface::class), + Mockery::mock(UserRepositoryInterface::class), + $this->domainService, + Mockery::mock(TwoFactorAuthenticationService::class), + Mockery::mock(UserSessionRevocationService::class), + Mockery::mock(SsoBreakGlassService::class), + Mockery::mock(UserIdentityRepositoryInterface::class), + ); + } + + public function test_an_email_off_every_usable_domain_can_sign_up(): void + { + $this->domainService->shouldReceive('findUsableDomainForEmail')->with('grace@acme.test')->andReturnNull(); + $this->connectionRepository->shouldNotReceive('findFirstWhere'); + + $this->service->assertSignupAllowed('grace@acme.test'); + + $this->addToAssertionCount(1); + } + + public function test_an_email_on_a_domain_whose_account_requires_sso_cannot_sign_up(): void + { + $this->givenConnection(AccountSsoConnectionStatus::ACTIVE, enforce: true); + + try { + $this->service->assertSignupAllowed('grace@acme.test'); + $this->fail('Expected SsoRequiredException'); + } catch (SsoRequiredException $exception) { + $this->assertSame('grace@acme.test', $exception->render()->getData(true)['context']['email']); + } + } + + public function test_an_email_on_a_domain_whose_connection_does_not_enforce_can_sign_up(): void + { + $this->givenConnection(AccountSsoConnectionStatus::ACTIVE, enforce: false); + + $this->service->assertSignupAllowed('grace@acme.test'); + + $this->addToAssertionCount(1); + } + + public function test_an_email_on_a_domain_whose_connection_is_off_can_sign_up(): void + { + $this->givenConnection(AccountSsoConnectionStatus::INACTIVE, enforce: true); + + $this->service->assertSignupAllowed('grace@acme.test'); + + $this->addToAssertionCount(1); + } + + private function givenConnection(AccountSsoConnectionStatus $status, bool $enforce): void + { + $this->domainService->shouldReceive('findUsableDomainForEmail') + ->with('grace@acme.test') + ->andReturn((new AccountSsoDomainDomainObject)->setAccountId(7)->setDomain('acme.test')); + $this->connectionRepository->shouldReceive('findFirstWhere') + ->with(['account_id' => 7]) + ->andReturn((new AccountSsoConnectionDomainObject)->setId(3)->setAccountId(7)->setStatus($status->value)->setEnforceSso($enforce)); + } +} diff --git a/backend/tests/Unit/Services/Domain/BoxOffice/BoxOfficeOperatorAuthorizerTest.php b/backend/tests/Unit/Services/Domain/BoxOffice/BoxOfficeOperatorAuthorizerTest.php index f193f2f956..32461e34a5 100644 --- a/backend/tests/Unit/Services/Domain/BoxOffice/BoxOfficeOperatorAuthorizerTest.php +++ b/backend/tests/Unit/Services/Domain/BoxOffice/BoxOfficeOperatorAuthorizerTest.php @@ -13,8 +13,8 @@ use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Services\Domain\Auth\Access\AccountUserAccess; use HiEvents\Services\Domain\Auth\Access\AccountUserAccessService; +use HiEvents\Services\Domain\Auth\AuthUserService; use HiEvents\Services\Infrastructure\Authorization\IsAuthorizedService; -use Illuminate\Auth\AuthManager; use Illuminate\Foundation\Application; use Mockery; use Mockery\MockInterface; @@ -28,7 +28,7 @@ class BoxOfficeOperatorAuthorizerTest extends TestCase private EventRepositoryInterface|MockInterface $eventRepository; - private AuthManager|MockInterface $authManager; + private AuthUserService|MockInterface $authUserService; private BoxOfficeOperatorAuthorizer $authorizer; @@ -37,7 +37,7 @@ protected function setUp(): void parent::setUp(); $this->eventRepository = Mockery::mock(EventRepositoryInterface::class); - $this->authManager = Mockery::mock(AuthManager::class); + $this->authUserService = Mockery::mock(AuthUserService::class); $container = Mockery::mock(Application::class); $container->shouldReceive('make') @@ -56,7 +56,7 @@ protected function setUp(): void $container, Mockery::mock(AccountUserRepositoryInterface::class), $accessService, - $this->authManager, + $this->authUserService, )); } @@ -109,7 +109,7 @@ public function test_a_viewer_cannot_operate_without_a_pin(): void public function test_a_deactivated_user_cannot_operate_without_a_pin(): void { - $this->authManager->shouldReceive('logout'); + $this->authUserService->shouldReceive('logout'); $this->assertFalse( $this->authorizer->canOperateWithoutPin( diff --git a/e2e/api/api-client.ts b/e2e/api/api-client.ts index 48fa8b5434..185d48470d 100644 --- a/e2e/api/api-client.ts +++ b/e2e/api/api-client.ts @@ -138,6 +138,15 @@ export class ApiClient { return unwrap(this.request.get('users/me', { headers: jsonHeaders })); } + createApiKey(payload: { + name: string; + access: 'READ_ONLY' | 'READ_WRITE'; + expires_in_days: number | null; + password: string; + }): Promise<{ id: number; key: string }> { + return unwrap<{ id: number; key: string }>(this.request.post('api-keys', { headers: jsonHeaders, data: payload })); + } + requestAccountDeletion(confirmation: string): Promise<{ id: number; status: string }> { return unwrap<{ id: number; status: string }>( this.request.post('accounts/deletion-request', { headers: jsonHeaders, data: { confirmation } }), diff --git a/e2e/pages/api-keys.page.ts b/e2e/pages/api-keys.page.ts new file mode 100644 index 0000000000..4bb2112027 --- /dev/null +++ b/e2e/pages/api-keys.page.ts @@ -0,0 +1,68 @@ +import type { Locator, Page } from '@playwright/test'; + +type Access = 'Read and write' | 'Read-only'; + +export class ApiKeysPage { + constructor(private readonly page: Page) {} + + async goto(): Promise { + await this.page.context().grantPermissions(['clipboard-read', 'clipboard-write']); + await this.page.goto('/settings/api-keys'); + await this.page.getByRole('heading', { level: 1, name: 'API keys' }).waitFor(); + await this.page.waitForLoadState('networkidle'); + } + + row(name: string): Locator { + return this.page.getByTestId('api-key-row').filter({ hasText: name }); + } + + emptyState(): Locator { + return this.page.getByTestId('api-keys-empty'); + } + + dialog(): Locator { + return this.page.getByRole('dialog'); + } + + async openCreate(): Promise { + await this.page.getByTestId('api-key-create-button').first().click(); + await this.dialog().getByRole('heading', { name: 'Create API key' }).waitFor(); + } + + async fillCreateForm(details: { name: string; access?: Access; expiry?: string; password: string }): Promise { + await this.page.getByTestId('api-key-name').fill(details.name); + if (details.access === 'Read-only') { + await this.page.getByTestId('api-key-access-read-only').click(); + } + if (details.expiry) { + await this.page.getByTestId('api-key-expiry').click(); + await this.page.getByRole('option', { name: details.expiry, exact: true }).click(); + } + await this.page.getByTestId('api-key-password').fill(details.password); + } + + async submitCreate(): Promise { + await this.page.getByTestId('api-key-create-submit').click(); + } + + async createKey(details: { name: string; access?: Access; expiry?: string; password: string }): Promise { + await this.openCreate(); + await this.fillCreateForm(details); + await this.submitCreate(); + await this.dialog().getByRole('heading', { name: 'Copy your API key' }).waitFor(); + const key = (await this.page.getByTestId('api-key-secret').textContent())?.trim() ?? ''; + await this.page.getByTestId('api-key-copy').click(); + await this.page.getByTestId('api-key-done').click(); + await this.dialog().waitFor({ state: 'hidden' }); + return key; + } + + async revoke(name: string): Promise { + await this.row(name).getByTestId('api-key-revoke').click(); + await this.page.getByRole('dialog').getByRole('button', { name: /^(Revoke|Remove) key$/ }).click(); + } + + async showEveryone(): Promise { + await this.page.getByTestId('api-key-scope').getByText(/^Everyone/).click(); + } +} diff --git a/e2e/pages/register.page.ts b/e2e/pages/register.page.ts index 1d7308baf8..566614b32d 100644 --- a/e2e/pages/register.page.ts +++ b/e2e/pages/register.page.ts @@ -1,4 +1,5 @@ import type { Page } from '@playwright/test'; +import { waitForHydration } from '../utils/hydration'; export class RegisterPage { constructor(private readonly page: Page) {} @@ -6,6 +7,7 @@ export class RegisterPage { async goto(): Promise { await this.page.goto('/auth/register'); await this.page.getByLabel(/^First Name/).waitFor(); + await waitForHydration(this.page, 'form'); } async register(details: { firstName: string; lastName?: string; email: string; password: string }): Promise { diff --git a/e2e/tests/account/api-keys.spec.ts b/e2e/tests/account/api-keys.spec.ts new file mode 100644 index 0000000000..ca8d5b5a8b --- /dev/null +++ b/e2e/tests/account/api-keys.spec.ts @@ -0,0 +1,336 @@ +import type { APIRequestContext, Page } from '@playwright/test'; +import { expect, test } from '../../fixtures'; +import { ApiKeysPage } from '../../pages/api-keys.page'; +import { expireApiKey } from '../../utils/db'; +import { invitedMember } from '../../utils/team'; +import { providerIsConfigured, SsoPage } from '../../pages/sso.page'; +import { uniqueEmail } from '../../utils/unique'; + +const MEMBER_PASSWORD = 'MemberPass123!'; + +const callApi = (api: APIRequestContext, apiKey: string, method: 'GET' | 'POST', path: string, data?: object) => + api.fetch(path, { + method, + data, + headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json', 'Content-Type': 'application/json' }, + }); + +const hasHorizontalOverflow = (page: Page) => + page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth); + +test.describe('API keys', () => { + test('an owner creates a key, uses it against the API and revokes it @smoke', async ({ freshAccount, mailpit, publicApi }) => { + const page = await freshAccount.newAuthedPage(); + const apiKeys = new ApiKeysPage(page); + + await apiKeys.goto(); + await expect(apiKeys.emptyState()).toContainText("You don't have any API keys"); + + await apiKeys.openCreate(); + await apiKeys.fillCreateForm({ name: 'Zapier production', expiry: '30 days', password: freshAccount.password }); + await expect(apiKeys.dialog()).toContainText("We'll email you a week before."); + await apiKeys.submitCreate(); + + await expect(apiKeys.dialog()).toContainText("This is the only time you'll see this key"); + const key = (await page.getByTestId('api-key-secret').textContent())?.trim() ?? ''; + expect(key).toMatch(/^hie_[A-Za-z0-9]{40,}$/); + await expect(apiKeys.dialog()).toContainText(`Authorization: Bearer ${key}`); + + await page.getByTestId('api-key-copy').click(); + await expect(page.getByTestId('api-key-copy')).toHaveText('Copied'); + expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(key); + await page.getByTestId('api-key-done').click(); + await expect(apiKeys.dialog()).toBeHidden(); + + const row = apiKeys.row('Zapier production'); + await expect(row).toContainText('Read and write'); + await expect(row).toContainText(`hie_…${key.slice(-4)}`); + await expect(row).toContainText('Never used'); + await expect(row).not.toContainText(key); + + const me = await callApi(publicApi, key, 'GET', 'users/me'); + expect(me.status()).toBe(200); + expect((await me.json()).data.email).toBe(freshAccount.email); + + await mailpit.waitForMessage(freshAccount.email, { subjectContains: 'A new API key was created' }); + + await page.reload(); + await expect(apiKeys.row('Zapier production')).not.toContainText('Never used'); + + await apiKeys.revoke('Zapier production'); + await expect(apiKeys.row('Zapier production')).toHaveCount(0); + await expect(apiKeys.emptyState()).toBeVisible(); + + const afterRevoke = await callApi(publicApi, key, 'GET', 'users/me'); + expect(afterRevoke.status()).toBe(401); + }); + + test('a read-only key that never expires can read but not change anything', async ({ freshAccount, publicApi }) => { + const page = await freshAccount.newAuthedPage(); + const apiKeys = new ApiKeysPage(page); + + await apiKeys.goto(); + await apiKeys.openCreate(); + await apiKeys.fillCreateForm({ name: 'Reporting', access: 'Read-only', expiry: 'No expiration', password: freshAccount.password }); + await expect(apiKeys.dialog()).toContainText('Keys that never expire stay valid until you revoke them.'); + await apiKeys.submitCreate(); + const key = (await page.getByTestId('api-key-secret').textContent())?.trim() ?? ''; + await page.getByTestId('api-key-copy').click(); + await page.getByTestId('api-key-done').click(); + + const row = apiKeys.row('Reporting'); + await expect(row).toContainText('Read-only'); + await expect(row).toContainText('Never'); + + const read = await callApi(publicApi, key, 'GET', `organizers/${freshAccount.organizerId}`); + expect(read.status()).toBe(200); + + const write = await callApi(publicApi, key, 'POST', 'organizers', { name: 'Nope', email: 'nope@example.com', currency: 'USD', timezone: 'UTC' }); + expect(write.status()).toBe(403); + expect((await write.json()).error_code).toBe('API_KEY_READ_ONLY'); + + const keysEndpoint = await callApi(publicApi, key, 'GET', 'api-keys'); + expect(keysEndpoint.status()).toBe(403); + expect((await keysEndpoint.json()).error_code).toBe('API_KEY_NOT_ALLOWED'); + }); + + test('a wrong password keeps the form open and creates nothing', async ({ freshAccount }) => { + const page = await freshAccount.newAuthedPage(); + const apiKeys = new ApiKeysPage(page); + + await apiKeys.goto(); + await apiKeys.openCreate(); + await apiKeys.fillCreateForm({ name: 'Wrong password', password: 'not-my-password' }); + await apiKeys.submitCreate(); + + await expect(apiKeys.dialog()).toContainText('The password is incorrect.'); + await expect(apiKeys.dialog().getByRole('heading', { name: 'Create API key' })).toBeVisible(); + await expect(page.getByTestId('api-key-name')).toHaveValue('Wrong password'); + + await apiKeys.dialog().getByRole('button', { name: 'Cancel' }).click(); + await expect(apiKeys.emptyState()).toBeVisible(); + }); + + test('closing the key before copying it asks for confirmation', async ({ freshAccount }) => { + const page = await freshAccount.newAuthedPage(); + const apiKeys = new ApiKeysPage(page); + + await apiKeys.goto(); + await apiKeys.openCreate(); + await apiKeys.fillCreateForm({ name: 'Careful', password: freshAccount.password }); + await apiKeys.submitCreate(); + await expect(page.getByTestId('api-key-secret')).toBeVisible(); + + await page.getByTestId('api-key-done').click(); + const warning = page.getByRole('dialog').filter({ hasText: "You haven't copied this key" }); + await expect(warning).toBeVisible(); + await warning.getByRole('button', { name: 'Go back' }).click(); + await expect(page.getByTestId('api-key-secret')).toBeVisible(); + + await page.getByTestId('api-key-done').click(); + await page.getByRole('dialog').getByRole('button', { name: 'Close without copying' }).click(); + await expect(page.getByTestId('api-key-secret')).toHaveCount(0); + await expect(apiKeys.row('Careful')).toBeVisible(); + }); + + test('when the browser blocks copying, the key can still be copied by hand', async ({ freshAccount }) => { + const page = await freshAccount.newAuthedPage(); + await page.addInitScript(() => { + Object.defineProperty(navigator, 'clipboard', { + value: { writeText: () => Promise.reject(new Error('blocked')) }, + }); + }); + const apiKeys = new ApiKeysPage(page); + + await apiKeys.goto(); + await apiKeys.openCreate(); + await apiKeys.fillCreateForm({ name: 'Blocked clipboard', password: freshAccount.password }); + await apiKeys.submitCreate(); + + await page.getByTestId('api-key-copy').click(); + await expect(apiKeys.dialog()).toContainText('Your browser blocked copying.'); + await expect(page.getByTestId('api-key-copy')).toHaveText('Copy key'); + + await page.getByTestId('api-key-done').click(); + const warning = page.getByRole('dialog').filter({ hasText: "You haven't copied this key" }); + await expect(warning).toBeVisible(); + await warning.getByRole('button', { name: 'Go back' }).click(); + + await page.getByTestId('api-key-secret').dispatchEvent('copy'); + await expect(page.getByTestId('api-key-copy')).toHaveText('Copied'); + await page.getByTestId('api-key-done').click(); + await expect(apiKeys.row('Blocked clipboard')).toBeVisible(); + }); + + test('an admin sees and revokes a member key, and the member is told', async ({ freshAccount, mailpit, publicApi, browser }) => { + const member = await invitedMember(freshAccount, mailpit, publicApi, browser, { last_name: 'Member', role: 'ORGANIZER' }); + const memberKeys = new ApiKeysPage(member.page); + await memberKeys.goto(); + const memberKey = await memberKeys.createKey({ name: 'Member integration', password: MEMBER_PASSWORD }); + + const ownerPage = await freshAccount.newAuthedPage(); + const ownerKeys = new ApiKeysPage(ownerPage); + await ownerKeys.goto(); + await expect(ownerKeys.row('Member integration')).toHaveCount(0); + await expect(ownerPage.getByTestId('api-key-create-button')).toHaveCount(1); + + await ownerKeys.showEveryone(); + const row = ownerKeys.row('Member integration'); + await expect(row).toContainText('Sam Member'); + await ownerKeys.revoke('Member integration'); + await expect(row).toHaveCount(0); + + await mailpit.waitForMessage(member.email, { subjectContains: 'One of your API keys was revoked' }); + expect((await callApi(publicApi, memberKey, 'GET', 'users/me')).status()).toBe(401); + + await memberKeys.goto(); + await expect(memberKeys.emptyState()).toBeVisible(); + }); + + test('members only see their own keys', async ({ freshAccount, mailpit, publicApi, browser }) => { + await freshAccount.api.createApiKey({ name: 'Owner key', access: 'READ_WRITE', expires_in_days: 30, password: freshAccount.password }); + const member = await invitedMember(freshAccount, mailpit, publicApi, browser, { last_name: 'Member', role: 'ORGANIZER' }); + + const memberKeys = new ApiKeysPage(member.page); + await memberKeys.goto(); + await expect(memberKeys.emptyState()).toBeVisible(); + await expect(memberKeys.row('Owner key')).toHaveCount(0); + await expect(member.page.getByTestId('api-key-scope')).toHaveCount(0); + }); + + test('a viewer can only create read-only keys', async ({ freshAccount, mailpit, publicApi, browser }) => { + const viewer = await invitedMember(freshAccount, mailpit, publicApi, browser, { last_name: 'Viewer', role: 'VIEWER' }); + const viewerKeys = new ApiKeysPage(viewer.page); + + await viewerKeys.goto(); + await viewerKeys.openCreate(); + await expect(viewer.page.getByTestId('api-key-access-read-write')).toBeDisabled(); + await expect(viewerKeys.dialog()).toContainText('Not available: you have view-only access.'); + + await viewerKeys.fillCreateForm({ name: 'Viewer reporting', password: MEMBER_PASSWORD }); + await viewerKeys.submitCreate(); + await viewer.page.getByTestId('api-key-copy').click(); + await viewer.page.getByTestId('api-key-done').click(); + + await expect(viewerKeys.row('Viewer reporting')).toContainText('Read-only'); + }); + + test('a read and write key can make changes, and stops working once it expires', async ({ freshAccount, publicApi }) => { + const created = await freshAccount.api.createApiKey({ name: 'Website sync', access: 'READ_WRITE', expires_in_days: 30, password: freshAccount.password }); + + const write = await callApi(publicApi, created.key, 'POST', 'organizers', { name: 'Made by the API', email: uniqueEmail('api-organizer'), currency: 'USD', timezone: 'UTC' }); + expect(write.status()).toBe(201); + const organizerId = (await write.json()).data.id; + + const page = await freshAccount.newAuthedPage(); + await page.goto(`/manage/organizer/${organizerId}`); + await expect(page.getByText('Made by the API').first()).toBeVisible(); + + expireApiKey(created.id); + const afterExpiry = await callApi(publicApi, created.key, 'GET', 'users/me'); + expect(afterExpiry.status()).toBe(401); + }); + + test('expired keys are grouped separately and can be removed', async ({ freshAccount }) => { + const created = await freshAccount.api.createApiKey({ name: 'Old CRM', access: 'READ_WRITE', expires_in_days: 30, password: freshAccount.password }); + await freshAccount.api.createApiKey({ name: 'Current sync', access: 'READ_WRITE', expires_in_days: 7, password: freshAccount.password }); + expireApiKey(created.id); + + const page = await freshAccount.newAuthedPage(); + const apiKeys = new ApiKeysPage(page); + await apiKeys.goto(); + + await expect(apiKeys.row('Current sync')).toContainText(/Expires in \d+ days/); + await expect(page.getByText(/^Expired\s*1$/)).toBeVisible(); + await expect(apiKeys.row('Old CRM')).toContainText('Expired 2 days ago'); + + await apiKeys.revoke('Old CRM'); + await expect(apiKeys.row('Old CRM')).toHaveCount(0); + await expect(apiKeys.row('Current sync')).toBeVisible(); + }); + + test('a single sign-on user confirms it is them and picks up where they left off', async ({ page }) => { + test.skip(!(await providerIsConfigured(page)), 'No OIDC provider is configured on this stack'); + + const email = uniqueEmail('sso-api'); + const subject = `subject-${email}`; + const sso = new SsoPage(page); + await page.goto('/auth/register'); + await sso.providerButton().click(); + await sso.signInAtProvider(subject, { email, email_verified: true, given_name: 'Ada', family_name: 'Lovelace' }); + await sso.completeSignup(); + await expect(page).toHaveURL(/\/welcome/); + + const apiKeys = new ApiKeysPage(page); + await apiKeys.goto(); + await apiKeys.openCreate(); + await page.getByTestId('api-key-name').fill('Signed in with SSO'); + await page.getByTestId('api-key-access-read-only').click(); + await expect(page.getByTestId('api-key-password')).toHaveCount(0); + await expect(page.getByTestId('api-key-create-submit')).toBeDisabled(); + + await apiKeys.dialog().getByTestId('sso-provider-button-oidc').click(); + await sso.signInAtProvider(subject, { email, email_verified: true }); + + await expect(apiKeys.dialog()).toContainText("You confirmed it's you."); + await expect(page.getByTestId('api-key-name')).toHaveValue('Signed in with SSO'); + await expect(page.getByTestId('api-key-access-read-only')).toBeChecked(); + await apiKeys.submitCreate(); + + await expect(page.getByTestId('api-key-secret')).toContainText('hie_'); + await page.getByTestId('api-key-copy').click(); + await page.getByTestId('api-key-done').click(); + await expect(apiKeys.row('Signed in with SSO')).toContainText('Read-only'); + }); + + test('the API reference opens from the API keys page and only lists what keys can call', async ({ freshAccount }) => { + const page = await freshAccount.newAuthedPage(); + const apiKeys = new ApiKeysPage(page); + + await apiKeys.goto(); + await page.getByTestId('api-reference-link').click(); + await expect(page).toHaveURL(/\/settings\/api-keys\/reference/); + + const reference = page.getByTestId('api-reference'); + await expect(reference.getByRole('heading', { name: /API$/, level: 1 })).toBeVisible({ timeout: 30_000 }); + await expect(reference).toContainText('API_KEY_READ_ONLY'); + await expect(reference.getByRole('link', { name: 'Events', exact: true })).toBeVisible(); + await expect(reference.getByRole('link', { name: 'Webhooks', exact: false }).first()).toBeVisible(); + await expect(reference.getByRole('link', { name: 'Api Keys', exact: true })).toHaveCount(0); + await expect(reference.getByRole('link', { name: 'Accounts / Sso', exact: true })).toHaveCount(0); + await expect(reference.getByText('Ask AI')).toHaveCount(0); + await expect(page.getByRole('link', { name: 'API keys' }).first()).toBeVisible(); + }); + + test.describe('on a phone', () => { + test.use({ viewport: { width: 375, height: 812 } }); + + test('the whole flow fits the screen', async ({ freshAccount }) => { + const page = await freshAccount.newAuthedPage(); + await page.setViewportSize({ width: 375, height: 812 }); + const apiKeys = new ApiKeysPage(page); + + await apiKeys.goto(); + expect(await hasHorizontalOverflow(page)).toBe(false); + + await apiKeys.openCreate(); + await apiKeys.fillCreateForm({ name: 'A key with a rather long descriptive name for a phone', password: freshAccount.password }); + await apiKeys.submitCreate(); + await expect(page.getByTestId('api-key-secret')).toBeVisible(); + await expect(page.getByTestId('api-key-copy')).toBeInViewport(); + expect(await hasHorizontalOverflow(page)).toBe(false); + await page.getByTestId('api-key-copy').click(); + await page.getByTestId('api-key-done').click(); + + const row = apiKeys.row('A key with a rather long'); + await expect(row).toBeVisible(); + await expect(row.getByText('Last used')).toBeVisible(); + await expect(row.getByTestId('api-key-revoke')).toBeInViewport(); + expect(await hasHorizontalOverflow(page)).toBe(false); + + await page.getByTestId('api-reference-link').click(); + await expect(page.getByTestId('api-reference').getByRole('heading', { name: /API$/, level: 1 })).toBeVisible({ timeout: 30_000 }); + expect(await hasHorizontalOverflow(page)).toBe(false); + }); + }); +}); diff --git a/e2e/utils/db.ts b/e2e/utils/db.ts index a574d30630..2bc821ff09 100644 --- a/e2e/utils/db.ts +++ b/e2e/utils/db.ts @@ -103,3 +103,15 @@ export function markSsoConnectionTested(accountId: number): void { } runSql(`UPDATE account_sso_connections SET last_tested_at = NOW() WHERE account_id = ${accountId}`); } + +export function expireApiKey(apiKeyId: number): void { + if (!Number.isInteger(apiKeyId)) { + throw new Error(`Refusing to expire an unexpected API key ${apiKeyId}`); + } + const updated = runSql( + `UPDATE personal_access_tokens SET expires_at = NOW() - INTERVAL '2 days' WHERE id = ${apiKeyId} RETURNING id`, + ).trim(); + if (updated === '') { + throw new Error(`No API key found with id ${apiKeyId}`); + } +} diff --git a/e2e/utils/hydration.ts b/e2e/utils/hydration.ts new file mode 100644 index 0000000000..ff1ddcd644 --- /dev/null +++ b/e2e/utils/hydration.ts @@ -0,0 +1,11 @@ +import type { Page } from '@playwright/test'; + +export async function waitForHydration(page: Page, selector: string): Promise { + await page.waitForFunction( + (target) => { + const element = document.querySelector(target); + return element !== null && Object.keys(element).some((key) => key.startsWith('__reactFiber')); + }, + selector, + ); +} diff --git a/frontend/package.json b/frontend/package.json index 1102b19159..bf3ffc230b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -40,6 +40,7 @@ "@react-pdf/renderer": "^4.5.1", "@react-router/node": "^7.1.5", "@remix-run/node": "^2.16.8", + "@scalar/api-reference-react": "0.9.77", "@sentry/node": "^10.73.0", "@stripe/react-stripe-js": "^2.1.1", "@stripe/stripe-js": "^1.54.1", diff --git a/frontend/src/api/apiKey.client.ts b/frontend/src/api/apiKey.client.ts new file mode 100644 index 0000000000..92ccd124ff --- /dev/null +++ b/frontend/src/api/apiKey.client.ts @@ -0,0 +1,16 @@ +import {api} from "./client.ts"; +import {ApiKeysResponse, CreateApiKeyRequest, CreatedApiKey, GenericDataResponse, IdParam} from "../types.ts"; + +export const apiKeyClient = { + all: async () => { + const response = await api.get('api-keys'); + return response.data; + }, + create: async (request: CreateApiKeyRequest) => { + const response = await api.post>('api-keys', request); + return response.data.data; + }, + delete: async (apiKeyId: IdParam) => { + await api.delete(`api-keys/${apiKeyId}`); + }, +}; diff --git a/frontend/src/api/apiReference.client.ts b/frontend/src/api/apiReference.client.ts new file mode 100644 index 0000000000..3cfe428ec9 --- /dev/null +++ b/frontend/src/api/apiReference.client.ts @@ -0,0 +1,8 @@ +import {api} from "./client.ts"; + +export const apiReferenceClient = { + get: async () => { + const response = await api.get>('api-reference'); + return response.data; + }, +}; diff --git a/frontend/src/components/common/SettingsPage/SettingsPage.module.scss b/frontend/src/components/common/SettingsPage/SettingsPage.module.scss index c14af7190b..8c3b2db085 100644 --- a/frontend/src/components/common/SettingsPage/SettingsPage.module.scss +++ b/frontend/src/components/common/SettingsPage/SettingsPage.module.scss @@ -58,6 +58,11 @@ display: flex; flex-shrink: 0; gap: 8px; + + @include mixins.respond-below(sm) { + flex-shrink: 1; + min-width: 0; + } } .body { diff --git a/frontend/src/components/layouts/SettingsLayout/index.tsx b/frontend/src/components/layouts/SettingsLayout/index.tsx index 668bd472d0..0f970059bf 100644 --- a/frontend/src/components/layouts/SettingsLayout/index.tsx +++ b/frontend/src/components/layouts/SettingsLayout/index.tsx @@ -5,6 +5,7 @@ import { IconAdjustmentsHorizontal, IconArrowLeft, IconBuildingStore, + IconKey, IconReceiptTax, IconShieldLock, IconUser, @@ -58,6 +59,7 @@ const SettingsLayout = () => { {link: '/settings/account', label: t`General`, icon: IconAdjustmentsHorizontal, showWhen: () => !isRestricted}, {link: '/settings/organizers', label: t`Organizers`, icon: IconBuildingStore, showWhen: () => !isRestricted}, {link: '/settings/team', label: t`Team`, icon: IconUsers, showWhen: () => !isRestricted && can('MANAGE_TEAM')}, + {link: '/settings/api-keys', label: t`API keys`, icon: IconKey}, {link: '/settings/taxes-and-fees', label: t`Taxes and fees`, icon: IconReceiptTax, showWhen: () => !isRestricted}, ]; @@ -66,6 +68,7 @@ const SettingsLayout = () => { const breadcrumbItems: BreadcrumbItem[] = [ {link: '/settings', content: t`Settings`}, ...(currentPage ? [{link: currentPage.link, content: currentPage.label}] : []), + ...(location.pathname.startsWith('/settings/api-keys/reference') ? [{link: '/settings/api-keys/reference', content: t`API reference`}] : []), ]; if (isLoaded && currentPage?.showWhen && !currentPage.showWhen()) { diff --git a/frontend/src/components/routes/auth/Login/index.tsx b/frontend/src/components/routes/auth/Login/index.tsx index 490e5d6a63..524bcbaf3f 100644 --- a/frontend/src/components/routes/auth/Login/index.tsx +++ b/frontend/src/components/routes/auth/Login/index.tsx @@ -31,7 +31,9 @@ const Login = () => { const [notice, setNotice] = useState((location.state as { notice?: string } | null)?.notice ?? null); const steps = useLoginSteps(); const {passwordLoginEnabled, registrationEnabled, enterpriseSsoEnabled} = useSsoOptions(); - const [ssoDiscoveryEmail, setSsoDiscoveryEmail] = useState((location.state as { ssoStep?: boolean } | null)?.ssoStep ? '' : null); + const [ssoDiscoveryEmail, setSsoDiscoveryEmail] = useState((location.state as { ssoStep?: boolean } | null)?.ssoStep + ? (location.state as { email?: string }).email ?? '' + : null); const [ssoRequiredEmail, setSsoRequiredEmail] = useState(null); const startSso = useStartSso(); diff --git a/frontend/src/components/routes/auth/Register/index.tsx b/frontend/src/components/routes/auth/Register/index.tsx index cd24442821..97f38e344a 100644 --- a/frontend/src/components/routes/auth/Register/index.tsx +++ b/frontend/src/components/routes/auth/Register/index.tsx @@ -46,6 +46,7 @@ export const Register = () => { const mutate = useRegisterAccount(); const [turnstileToken, setTurnstileToken] = useState(''); const [formError, setFormError] = useState(null); + const [ssoRequiredEmail, setSsoRequiredEmail] = useState(null); const {passwordLoginEnabled, registrationEnabled} = useSsoOptions(); const [turnstileResetKey, setTurnstileResetKey] = useState(0); @@ -65,6 +66,11 @@ export const Register = () => { onError: (error: any) => { setTurnstileResetKey((key) => key + 1); + if (error?.response?.data?.error_code === 'SSO_REQUIRED') { + setSsoRequiredEmail(data.email); + return; + } + if (error?.response?.data?.errors) { errorHandler(form, error); return; @@ -100,6 +106,17 @@ export const Register = () => {

+ {ssoRequiredEmail !== null && ( + + {t`Accounts for this email address are managed by your organization. Sign in through its identity provider instead of creating a new account.`} +
+ +
+
+ )} {formError && ( setFormError(null)}>{formError} )} diff --git a/frontend/src/components/routes/settings/ApiKeys/ApiKeyRow.tsx b/frontend/src/components/routes/settings/ApiKeys/ApiKeyRow.tsx new file mode 100644 index 0000000000..858cf54522 --- /dev/null +++ b/frontend/src/components/routes/settings/ApiKeys/ApiKeyRow.tsx @@ -0,0 +1,96 @@ +import {ActionIcon, Badge, Tooltip, VisuallyHidden} from "@mantine/core"; +import {t} from "@lingui/macro"; +import {IconKey, IconTrash} from "@tabler/icons-react"; +import classNames from "classnames"; +import dayjs from "dayjs"; +import {ApiKey} from "../../../../types.ts"; +import {formatDateWithLocale, relativeDate} from "../../../../utilites/dates.ts"; +import classes from "./ApiKeys.module.scss"; + +const EXPIRING_SOON_DAYS = 7; + +interface ApiKeyRowProps { + apiKey: ApiKey; + timezone: string; + locale?: string; + showOwner: boolean; + onRevoke: () => void; +} + +const ExpiryCell = ({apiKey, timezone, locale}: Pick) => { + if (!apiKey.expires_at) { + return <>{t`Expires`}{t`Never`}; + } + + if (apiKey.is_expired) { + return {t`Expired ${relativeDate(apiKey.expires_at)}`}; + } + + if (dayjs.utc(apiKey.expires_at).diff(dayjs(), 'day', true) <= EXPIRING_SOON_DAYS) { + return {t`Expires ${relativeDate(apiKey.expires_at)}`}; + } + + return <>{t`Expires`}{formatDateWithLocale(apiKey.expires_at, 'shortDate', timezone, locale)}; +}; + +export const ApiKeyRow = ({apiKey, timezone, locale, showOwner, onRevoke}: ApiKeyRowProps) => { + const revokeLabel = apiKey.is_expired ? t`Remove ${apiKey.name}` : t`Revoke ${apiKey.name}`; + + return ( +
+
+ +
+
+ {apiKey.name} + + {apiKey.access === 'READ_WRITE' ? t`Read and write` : t`Read-only`} + +
+
+ hie_…{apiKey.token_hint} + {t`Key ending in ${apiKey.token_hint}`} + {showOwner && apiKey.owner && ( + + {apiKey.is_own ? t`You` : apiKey.owner.full_name} + + )} +
+
+
+ +
+ + {t`Created`} + {formatDateWithLocale(apiKey.created_at, 'shortDate', timezone, locale)} + + + {t`Last used`} + {apiKey.last_used_at ? relativeDate(apiKey.last_used_at) : t`Never used`} + + + + +
+ +
+ + + + + +
+
+ ); +}; diff --git a/frontend/src/components/routes/settings/ApiKeys/ApiKeys.module.scss b/frontend/src/components/routes/settings/ApiKeys/ApiKeys.module.scss new file mode 100644 index 0000000000..8ecf636f2e --- /dev/null +++ b/frontend/src/components/routes/settings/ApiKeys/ApiKeys.module.scss @@ -0,0 +1,461 @@ +@use "../../../../styles/mixins.scss"; + +$row-columns: minmax(0, 1fr) 120px 130px 150px 36px; + +.stack { + container-type: inline-size; + display: flex; + flex-direction: column; + gap: 16px; +} + +.panel { + overflow: hidden; + border: 1px solid var(--hi-color-border); + border-radius: var(--hi-radius-card); + background: var(--hi-color-white); +} + +.toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 10px 16px; + border-bottom: 1px solid var(--hi-color-border); +} + +.toolbarCount { + font-size: 0.8125rem; + font-variant-numeric: tabular-nums; + color: var(--hi-color-text-muted); +} + +.columns, +.row { + display: grid; + grid-template-columns: $row-columns; + align-items: center; + gap: 16px; + padding: 0 16px; +} + +.columns { + height: 34px; + border-bottom: 1px solid var(--hi-color-border); + background: var(--hi-color-gray); + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--hi-color-text-muted); + + @include mixins.respond-below(md, true) { + display: none; + } +} + +.row { + min-height: 64px; + padding-top: 12px; + padding-bottom: 12px; + border-bottom: 1px solid var(--hi-color-border); + + &:last-child { + border-bottom: 0; + } + + @include mixins.respond-below(md, true) { + grid-template-columns: minmax(0, 1fr) auto; + row-gap: 8px; + } +} + +.rowExpired .identity, +.rowExpired .cell:not(.cellExpires) { + opacity: 0.6; +} + +.identity { + display: flex; + align-items: center; + gap: 12px; + min-width: 0; +} + +.keyIcon { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 10px; + background: var(--hi-color-gray); + color: var(--hi-color-text-muted); + + @include mixins.respond-below(sm, true) { + display: none; + } +} + +.identityText { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; +} + +.nameLine { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + + :global(.mantine-Badge-root) { + flex-shrink: 0; + } +} + +.name { + @include mixins.ellipsis(); + min-width: 0; + font-size: 0.875rem; + font-weight: 600; + color: var(--hi-text); + + @include mixins.respond-below(sm) { + white-space: normal; + overflow-wrap: anywhere; + } +} + +.subLine { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 2px 10px; + min-width: 0; + font-size: 0.75rem; + color: var(--hi-color-text-muted); +} + +.hint { + font-family: var(--mantine-font-family-monospace); + font-size: 0.75rem; + letter-spacing: 0.02em; +} + +.owner { + @include mixins.ellipsis(); + max-width: 220px; +} + +.meta { + display: contents; + + @include mixins.respond-below(md, true) { + display: flex; + grid-column: 1 / -1; + flex-wrap: wrap; + gap: 4px 16px; + padding-left: 46px; + + @include mixins.respond-below(sm, true) { + padding-left: 0; + } + } +} + +.cell { + min-width: 0; + font-size: 0.8125rem; + white-space: nowrap; + color: var(--hi-color-text-muted); + + @include mixins.respond-below(md, true) { + font-size: 0.75rem; + } +} + +.cellLabel { + display: none; + + @include mixins.respond-below(md, true) { + display: inline; + margin-right: 4px; + color: var(--hi-color-text-tertiary); + } +} + +.expiringSoon { + font-weight: 500; + color: var(--hi-color-warning); +} + +.expired { + font-weight: 500; + color: var(--hi-color-danger); +} + +.cellActions { + display: flex; + justify-content: flex-end; + + @include mixins.respond-below(md, true) { + grid-row: 1; + grid-column: 2; + } +} + +.groupLabel { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + border-top: 1px solid var(--hi-color-border); + border-bottom: 1px solid var(--hi-color-border); + background: var(--hi-color-gray); + font-size: 0.75rem; + font-weight: 600; + color: var(--hi-color-text-muted); + + &:first-child { + border-top: 0; + } +} + +.groupCount { + padding: 0 6px; + border-radius: 999px; + background: var(--hi-color-white); + font-variant-numeric: tabular-nums; +} + +.empty { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 48px 24px; + text-align: center; +} + +.emptyIcon { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + margin-bottom: 4px; + border-radius: 12px; + background: var(--hi-accent-brand-soft); + color: var(--mantine-color-primary-6); +} + +.emptyTitle { + font-size: 0.9375rem; + font-weight: 600; + color: var(--hi-text); +} + +.emptyText { + max-width: 420px; + margin: 0 0 8px; + font-size: 0.8125rem; + line-height: 1.55; + color: var(--hi-color-text-muted); +} + +.guide { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); + gap: 20px; + padding: 18px 20px; + border: 1px solid var(--hi-color-border); + border-radius: var(--hi-radius-card); + background: var(--hi-color-white); + + @include mixins.respond-below(md, true) { + grid-template-columns: 1fr; + gap: 14px; + padding: 16px; + } +} + +.guideTitle { + margin: 0 0 8px; + font-size: 0.875rem; + font-weight: 600; + color: var(--hi-text); +} + +.guideList { + display: flex; + flex-direction: column; + gap: 6px; + margin: 0; + padding-left: 18px; + font-size: 0.8125rem; + line-height: 1.5; + color: var(--hi-color-text-muted); +} + +.guideLink { + display: inline-flex; + align-items: center; + gap: 4px; + margin-top: 10px; + font-size: 0.8125rem; + font-weight: 500; +} + +.snippet { + position: relative; + min-width: 0; +} + +.snippetCode { + margin: 0; + padding: 12px 44px 12px 14px; + overflow-wrap: anywhere; + border-radius: 10px; + background: var(--hi-color-gray); + font-family: var(--mantine-font-family-monospace); + font-size: 0.75rem; + line-height: 1.7; + white-space: pre-wrap; + color: var(--hi-text); +} + +.snippetCopy { + position: absolute; + top: 6px; + right: 6px; +} + +.modalBody { + display: flex; + flex-direction: column; + gap: 1.125rem; + + :global(.mantine-InputWrapper-root) { + margin-bottom: 0; + } +} + +.modalText { + margin: 0; + font-size: 0.875rem; + line-height: 1.55; + color: var(--hi-color-text-muted); +} + +.fieldLabel { + margin-bottom: 0.5rem; + font-size: 0.875rem; + font-weight: 500; + color: var(--hi-text); +} + +.choices { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + + @include mixins.respond-below(sm) { + grid-template-columns: 1fr; + } +} + +.choice { + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px 14px; + border: 1px solid var(--hi-color-border); + border-radius: 10px; + background: var(--hi-color-white); + transition: border-color 120ms ease, background-color 120ms ease; + + &:hover:not(:disabled, [data-disabled]) { + border-color: var(--mantine-color-primary-3); + } + + &[data-checked] { + border-color: var(--mantine-color-primary-5); + background: var(--hi-accent-brand-soft); + } + + &:disabled, + &[data-disabled] { + cursor: not-allowed; + opacity: 0.55; + } +} + +.choiceHead { + display: flex; + align-items: center; + justify-content: space-between; +} + +.choiceLabel { + font-size: 0.875rem; + font-weight: 600; + color: var(--hi-text); +} + +.choiceDescription { + font-size: 0.75rem; + line-height: 1.45; + color: var(--hi-color-text-muted); +} + +.expiryNote { + margin-top: 6px; + font-size: 0.75rem; + color: var(--hi-color-text-muted); +} + +.expiryWarning { + color: var(--hi-color-warning); +} + +.secret { + display: flex; + align-items: stretch; + gap: 8px; + + @include mixins.respond-below(sm) { + flex-direction: column; + } +} + +.secretValue { + flex: 1; + min-width: 0; + padding: 11px 14px; + overflow-wrap: anywhere; + border: 1px solid var(--hi-color-border); + border-radius: 10px; + background: var(--hi-color-gray); + font-family: var(--mantine-font-family-monospace); + font-size: 0.8125rem; + line-height: 1.5; + color: var(--hi-text); + user-select: all; +} + +.secretCopy { + flex-shrink: 0; + height: auto; + min-height: 42px; +} + +.copyFailed { + margin: 8px 0 0; + font-size: 0.8125rem; + color: var(--hi-color-warning); +} diff --git a/frontend/src/components/routes/settings/ApiKeys/CodeSnippet.tsx b/frontend/src/components/routes/settings/ApiKeys/CodeSnippet.tsx new file mode 100644 index 0000000000..472452b83c --- /dev/null +++ b/frontend/src/components/routes/settings/ApiKeys/CodeSnippet.tsx @@ -0,0 +1,33 @@ +import {useEffect} from "react"; +import {ActionIcon, Tooltip} from "@mantine/core"; +import {useClipboard} from "@mantine/hooks"; +import {t} from "@lingui/macro"; +import {IconCheck, IconCopy} from "@tabler/icons-react"; +import classes from "./ApiKeys.module.scss"; + +export const CodeSnippet = ({code, onCopy}: { code: string, onCopy?: () => void }) => { + const clipboard = useClipboard({timeout: 2000}); + + useEffect(() => { + if (clipboard.copied) { + onCopy?.(); + } + }, [clipboard.copied]); + + return ( +
+
{code}
+ + clipboard.copy(code)} + > + {clipboard.copied ? : } + + +
+ ); +}; diff --git a/frontend/src/components/routes/settings/ApiKeys/CreateApiKeyModal.tsx b/frontend/src/components/routes/settings/ApiKeys/CreateApiKeyModal.tsx new file mode 100644 index 0000000000..2614d2e6d2 --- /dev/null +++ b/frontend/src/components/routes/settings/ApiKeys/CreateApiKeyModal.tsx @@ -0,0 +1,308 @@ +import {Button, Radio, Select, TextInput} from "@mantine/core"; +import {useForm} from "@mantine/form"; +import {t} from "@lingui/macro"; +import {IconCheck, IconCopy} from "@tabler/icons-react"; +import dayjs from "dayjs"; +import {FormEvent, useState} from "react"; +import classNames from "classnames"; +import {Modal, ModalFooter} from "../../../common/Modal"; +import {Callout} from "../../../common/Callout"; +import {ConfirmIdentityField, useIdentityConfirmation} from "../../../common/ConfirmIdentity"; +import {useCreateApiKey} from "../../../../mutations/useApiKeyMutations.ts"; +import {resumeUrl} from "../../../../hooks/useResumeAction.ts"; +import {confirmationDialog} from "../../../../utilites/confirmationDialog.tsx"; +import {formatDateWithLocale} from "../../../../utilites/dates.ts"; +import {safeSessionStorageGet, safeSessionStorageRemove, safeSessionStorageSet} from "../../../../utilites/helpers.ts"; +import {showError} from "../../../../utilites/notifications.tsx"; +import {ApiKeyAccess, CreatedApiKey} from "../../../../types.ts"; +import {API_KEYS_SETTINGS_PATH, CREATE_API_KEY_RESUME_ACTION} from "./constants.ts"; +import {useApiBaseUrl} from "./useApiBaseUrl.ts"; +import {CodeSnippet} from "./CodeSnippet.tsx"; +import classes from "./ApiKeys.module.scss"; + +const DRAFT_STORAGE_KEY = 'hi-api-key-draft'; +const NEVER = 'never'; +const DEFAULT_EXPIRY_DAYS = 90; + +interface Draft { + name: string; + access: ApiKeyAccess; + expires: string; +} + +const readDraft = (): Partial => { + try { + return JSON.parse(safeSessionStorageGet(DRAFT_STORAGE_KEY) ?? '{}'); + } catch { + return {}; + } +}; + +const expiryLabel = (days: number) => { + if (days === 365) { + return t`1 year`; + } + + return days === 1 ? t`1 day` : t`${days} days`; +}; + +interface CreateApiKeyModalProps { + canEdit: boolean; + accountName?: string; + expiryOptions: number[]; + timezone: string; + locale?: string; + restoreDraft: boolean; + onClose: () => void; +} + +export const CreateApiKeyModal = ({canEdit, accountName, expiryOptions, timezone, locale, restoreDraft, onClose}: CreateApiKeyModalProps) => { + const identity = useIdentityConfirmation(); + const createApiKey = useCreateApiKey(); + const apiBaseUrl = useApiBaseUrl(); + const [created, setCreated] = useState(null); + const [formError, setFormError] = useState(null); + const [copyState, setCopyState] = useState<'idle' | 'copied' | 'failed'>('idle'); + const copied = copyState === 'copied'; + + const [draft] = useState>(() => restoreDraft ? readDraft() : {}); + + const form = useForm({ + initialValues: { + name: draft.name ?? '', + access: canEdit ? (draft.access ?? 'READ_WRITE') : 'READ_ONLY', + expires: draft.expires ?? String(DEFAULT_EXPIRY_DAYS), + }, + validate: { + name: (value) => value.trim().length === 0 ? t`Give this key a name` : null, + }, + onValuesChange: (values) => safeSessionStorageSet(DRAFT_STORAGE_KEY, JSON.stringify(values)), + }); + + const expiresInDays = form.values.expires === NEVER ? null : Number(form.values.expires); + const expiryDate = expiresInDays === null ? null : dayjs.utc().add(expiresInDays, 'day').toISOString(); + + const closeAndForgetDraft = () => { + safeSessionStorageRemove(DRAFT_STORAGE_KEY); + onClose(); + }; + + const submit = (event?: FormEvent) => { + event?.preventDefault(); + + if (form.validate().hasErrors || !identity.confirmation) { + return; + } + + setFormError(null); + + createApiKey.mutate({ + ...identity.confirmation, + name: form.values.name.trim(), + access: form.values.access, + expires_in_days: expiresInDays, + }, { + onSuccess: (apiKey) => { + safeSessionStorageRemove(DRAFT_STORAGE_KEY); + setCreated(apiKey); + }, + onError: (error: any) => { + const data = error?.response?.data; + const errors = data?.errors ?? {}; + + identity.setError(errors.password?.[0] ?? null); + form.setErrors({ + name: errors.name?.[0], + access: errors.access?.[0], + expires: errors.expires_in_days?.[0], + }); + + if (Object.keys(errors).length === 0) { + const message = data?.message ?? t`Something went wrong. Please try again.`; + + if (error?.response?.status >= 500 || !error?.response) { + showError(message); + } else { + setFormError(message); + } + } + }, + }); + }; + + const copyKey = async () => { + if (!created) { + return; + } + + try { + await navigator.clipboard.writeText(created.key); + setCopyState('copied'); + } catch { + setCopyState('failed'); + } + }; + + const finish = () => { + if (copied) { + onClose(); + return; + } + + confirmationDialog( + t`You haven't copied this key. Once you close this window it can't be shown again.`, + onClose, + {confirm: t`Close without copying`, cancel: t`Go back`, destructive: true}, + ); + }; + + if (created) { + return ( + +
+ + {t`Store it somewhere safe, like a password manager or your integration's secret settings. Anyone with this key can use the API as you.`} + + +
+
{created.name}
+
+ setCopyState('copied')} + data-testid="api-key-secret" + > + {created.key} + + +
+ {copyState === 'failed' && ( +

+ {t`Your browser blocked copying. Select the key and copy it yourself.`} +

+ )} +
+ +
+
{t`Try it`}
+ setCopyState('copied')} + /> +
+ + + + +
+
+ ); + } + + return ( + +
+ {accountName && ( +

+ {t`The key can do what you can do in ${accountName}, so keep it secret.`} +

+ )} + + {formError && {formError}} + + + + form.setFieldValue('access', value as ApiKeyAccess)} + > +
+ + + {t`Read and write`} + + + + {canEdit + ? t`Create and change events, orders, attendees and more.` + : t`Not available: you have view-only access.`} + + + + + {t`Read-only`} + + + + {t`View data and run exports. Can't change anything.`} + + +
+
+ +
+