{"openapi":"3.0.0","paths":{"/health":{"get":{"operationId":"healthCheck","summary":"Health check","description":"Check the health status of the API Gateway and its connected backend services. This endpoint is publicly accessible and requires no authentication. Use this endpoint for uptime monitoring, load balancer health checks, and service availability verification.","parameters":[],"responses":{"200":{"description":"Service is healthy and all backend services are connected","content":{"application/json":{"schema":{"example":{"success":true,"message":"API Gateway is healthy","timestamp":"2025-11-03T12:00:00.000Z","services":{"iam":"connected","link":"connected","bill":"connected","biller":"connected","billing":"connected","email":"connected","metering":"connected","internal_iam":"connected","debug":"connected"}}}}}},"503":{"description":"Service is unhealthy or backend services are unavailable"}},"tags":["Health"]}},"/health/live":{"get":{"operationId":"live","summary":"Liveness (gateway process only)","parameters":[],"responses":{"200":{"description":"Gateway is running"}},"tags":["Health"]}},"/health/services":{"get":{"operationId":"getServicesHealth","summary":"Per-service health (detailed)","description":"Returns detailed health status and response times for each backend service. Used by the internal portal status page.","parameters":[],"responses":{"200":{"description":"Per-service health with response times","content":{"application/json":{"schema":{"example":{"iam":{"status":"healthy","response_time_ms":12},"link":{"status":"healthy","response_time_ms":8},"bill":{"status":"healthy","response_time_ms":15},"biller":{"status":"healthy","response_time_ms":10},"billing":{"status":"unhealthy","response_time_ms":null,"error":"Service not found in Consul"},"email":{"status":"healthy","response_time_ms":11},"metering":{"status":"healthy","response_time_ms":9},"internal_iam":{"status":"healthy","response_time_ms":14},"debug":{"status":"unhealthy","response_time_ms":null,"error":"Service not deployed"}}}}}}},"tags":["Health"]}},"/health/metadata":{"get":{"operationId":"getMetadata","summary":"Service metadata","description":"Returns display names, categories, and required flags for all monitored services.","parameters":[],"responses":{"200":{"description":"Service metadata list"}},"tags":["Health"]}},"/health/ready":{"get":{"operationId":"getReady","summary":"Readiness check","description":"Returns 200 if the API Gateway process is up and all required backend services are healthy. Use for Kubernetes readinessProbe and post-deploy verification. Returns 503 if any required backend is unhealthy.","parameters":[],"responses":{"200":{"description":"Ready to accept traffic"},"503":{"description":"Not ready - required backends unhealthy"}},"tags":["Health"]}},"/iam/auth/signin":{"post":{"operationId":"signIn","summary":"Sign in user","description":"Authenticate a user with email and password credentials. Returns JWT tokens (access, refresh, and ID tokens) that can be used to access protected endpoints. The access token expires in 1 hour and should be included in the Authorization header as \"Bearer <token>\". Use the refresh token with /iam/auth/refresh to obtain a new access token without re-authenticating.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInDto"},"examples":{"example1":{"summary":"Basic signin","value":{"email":"developer@example.com","password":"SecurePassword123!"}}}}}},"responses":{"200":{"description":"Successfully authenticated - returns JWT tokens and user information","content":{"application/json":{"schema":{"example":{"success":true,"message":"Successfully signed in","tokens":{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsImVtYWlsIjoiZGV2ZWxvcGVyQGV4YW1wbGUuY29tIiwiaWF0IjoxNjk5MDAwMDAwLCJleHAiOjE2OTkwMDM2MDB9...","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsInR5cGUiOiJyZWZyZXNoIiwiaWF0IjoxNjk5MDAwMDAwLCJleHAiOjE3MDE1OTIwMDB9...","idToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsImVtYWlsIjoiZGV2ZWxvcGVyQGV4YW1wbGUuY29tIiwibmFtZSI6IkpvaG4gRG9lIn0...","expiresIn":3600},"user":{"id":"user-123","email":"developer@example.com","name":"John Doe","createdAt":"2025-10-01T00:00:00.000Z"}}}}}},"400":{"description":"Bad request - missing or invalid fields","content":{"application/json":{"schema":{"example":{"success":false,"message":"Validation failed","errors":["email must be a valid email","password is required"],"statusCode":400}}}}},"401":{"description":"Authentication failed - invalid email or password","content":{"application/json":{"schema":{"example":{"success":false,"message":"Invalid email or password","statusCode":401}}}}}},"tags":["Authentication"]}},"/iam/auth/signup":{"post":{"operationId":"signUp","summary":"Sign up new user","description":"Register a new user account to access the BillerAPI platform. After successful registration, an email confirmation code will be sent to the provided email address. Use the /iam/auth/confirm-signup endpoint with the code to verify your email before signing in. Password must be at least 8 characters with uppercase, lowercase, numbers, and special characters.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"},"examples":{"example1":{"summary":"New user registration","value":{"email":"newdev@example.com","password":"SecurePass123!","name":"Jane Developer","companyName":"FinTech Startup Inc"}}}}}},"responses":{"201":{"description":"User created successfully - check email for confirmation code","content":{"application/json":{"schema":{"example":{"success":true,"message":"User created successfully. Please check your email for confirmation code.","user":{"id":"user-456","email":"newdev@example.com","name":"Jane Developer","emailVerified":false}}}}}},"400":{"description":"Bad request - validation failed or email already exists","content":{"application/json":{"schema":{"example":{"success":false,"message":"Email already exists","statusCode":400}}}}}},"tags":["Authentication"]}},"/iam/auth/signout":{"post":{"operationId":"signOut","summary":"Sign out user","description":"Invalidate user access token","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignOutDto"}}}},"responses":{"200":{"description":"Successfully signed out"}},"tags":["Authentication"]}},"/iam/auth/confirm-signup":{"post":{"operationId":"confirmSignUp","summary":"Confirm email signup","description":"Verify email with confirmation code","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmSignUpDto"}}}},"responses":{"200":{"description":"Email confirmed successfully"},"400":{"description":"Invalid or expired code"}},"tags":["Authentication"]}},"/iam/auth/confirm-and-signin":{"post":{"operationId":"confirmAndSignIn","summary":"Confirm email and sign in","description":"Confirm email with verification code and sign in. Returns tokens and user (same shape as signin). Use when the user has just verified their email and should be logged in without a separate sign-in step.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmSignUpAndSignInDto"}}}},"responses":{"200":{"description":"Email confirmed and signed in - returns JWT tokens and user information","content":{"application/json":{"schema":{"example":{"success":true,"message":"Email confirmed and signed in","tokens":{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","idToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","expiresIn":3600},"user":{"id":"user-123","email":"developer@example.com","given_name":"John","family_name":"Doe"}}}}}},"400":{"description":"Invalid or expired code"},"401":{"description":"Invalid password after confirmation"}},"tags":["Authentication"]}},"/iam/auth/forgot-password":{"post":{"operationId":"forgotPassword","summary":"Request password reset","description":"Send password reset code to email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordDto"}}}},"responses":{"200":{"description":"Reset code sent"}},"tags":["Authentication"]}},"/iam/auth/reset-password":{"post":{"operationId":"resetPassword","summary":"Reset password with code","description":"Set new password using reset code","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordDto"}}}},"responses":{"200":{"description":"Password reset successfully"},"400":{"description":"Invalid or expired code"}},"tags":["Authentication"]}},"/iam/auth/refresh":{"post":{"operationId":"refreshToken","summary":"Refresh access token","description":"Get new access token using refresh token","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenDto"}}}},"responses":{"200":{"description":"New access token issued","content":{"application/json":{"schema":{"example":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","expires_in":3600}}}}},"401":{"description":"Invalid or expired refresh token"}},"tags":["Authentication"]}},"/iam/auth/email/request-verification":{"post":{"operationId":"requestEmailVerification","summary":"Request an email verification code","description":"Sends a 6-digit code to the authenticated user's email address via Resend. Required before transitioning from sandbox to development environment. Returns 429 with cooldown_seconds when called within 5 minutes of a previous request.","parameters":[],"responses":{"200":{"description":"Code generated and email send attempted","content":{"application/json":{"schema":{"example":{"success":true,"message":"Verification code sent. Check your email.","cooldown_seconds":300,"error_code":""}}}}},"401":{"description":"Unauthorized"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/auth/email/verify":{"post":{"operationId":"verifyEmail","summary":"Verify an email verification code","description":"Submits a 6-digit code received by email. On success, the user's Client.emailVerified flag is set to true and the dashboard banner disappears. After 5 wrong attempts, the user is locked out for 15 minutes.","parameters":[],"responses":{"200":{"description":"Verification attempted (check success field)","content":{"application/json":{"schema":{"example":{"success":true,"message":"Email verified.","error_code":""}}}}},"401":{"description":"Unauthorized"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/auth/change-password":{"post":{"operationId":"changePassword","summary":"Change password (authenticated)","description":"Rotate the signed-in user's password. On success the server invokes AdminUserGlobalSignOut to invalidate ALL refresh tokens (including any other devices the user is signed in on), then re-issues a fresh token pair for the current session. The bb_client_auth_token cookie is rotated in place.","parameters":[],"responses":{"200":{"description":"Password change attempted (check success field)","content":{"application/json":{"schema":{"example":{"success":true,"message":"Password updated.","error_code":""}}}}},"401":{"description":"Unauthorized"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/auth/me":{"get":{"operationId":"getProfile","summary":"Get user profile","description":"Retrieve the authenticated user's profile information including email, name, and account details. Requires a valid JWT access token in the Authorization header.","parameters":[],"responses":{"200":{"description":"User profile retrieved successfully","content":{"application/json":{"schema":{"example":{"success":true,"user":{"id":"user-123","email":"developer@example.com","name":"John Doe","companyName":"My Company","emailVerified":true,"createdAt":"2025-10-01T00:00:00.000Z","updatedAt":"2025-11-01T00:00:00.000Z"}}}}}},"401":{"description":"Unauthorized - invalid or expired token","content":{"application/json":{"schema":{"example":{"success":false,"message":"Unauthorized","statusCode":401}}}}}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/verification-request":{"post":{"operationId":"submitVerificationRequest","summary":"Submit verification request","description":"Submit business/use-case form to request production access. A team member will review and verify your client.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitVerificationRequestDto"}}}},"responses":{"200":{"description":"Verification request submitted"},"400":{"description":"Already verified or invalid request"},"401":{"description":"Unauthorized"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/request-live-access":{"post":{"operationId":"requestLiveAccess","summary":"Request live (dev + prod) access","description":"#2404 — explicit user-driven request to flip live-access on. Requires email verified, business form submitted, and billing setup completed. In dev, auto-approves. In prod, the handler will queue an operator review (not yet wired).","parameters":[],"responses":{"200":{"description":"Live access granted"},"400":{"description":"Prerequisites not met"},"401":{"description":"Unauthorized"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients":{"get":{"operationId":"listClients","summary":"List clients","description":"Get all API clients for the authenticated user","parameters":[],"responses":{"200":{"description":"List of clients","content":{"application/json":{"schema":{"example":{"clients":[{"id":"client-123","name":"My Application","createdAt":"2025-11-01T00:00:00Z"}]}}}}}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]},"post":{"operationId":"createClient","summary":"Create client application","description":"Create a new API client to access BillerAPI services. Each client represents an application or integration that will use the BillerAPI API. After creating a client, use the /iam/clients/:id/client-secrets endpoint to generate credentials (client_id and client_secret) for each environment.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClientDto"},"examples":{"example1":{"summary":"Production application","value":{"name":"My Bill Pay App","description":"Production bill payment integration","redirectUris":["https://myapp.com/callback"],"allowedOrigins":["https://myapp.com"]}}}}}},"responses":{"201":{"description":"Client created successfully - now generate client secrets for each environment","content":{"application/json":{"schema":{"example":{"success":true,"message":"Client created successfully","client":{"id":"client-abc123","name":"My Bill Pay App","description":"Production bill payment integration","userId":"user-123","redirectUris":["https://myapp.com/callback"],"allowedOrigins":["https://myapp.com"],"createdAt":"2025-11-01T00:00:00.000Z","updatedAt":"2025-11-01T00:00:00.000Z"}}}}}},"400":{"description":"Bad request - invalid client configuration","content":{"application/json":{"schema":{"example":{"success":false,"message":"Validation failed","errors":["name is required","redirectUris must be valid URLs"],"statusCode":400}}}}}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients/{id}":{"get":{"operationId":"getClient","summary":"Get client by ID","description":"Retrieve client details","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Client details"},"404":{"description":"Client not found"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]},"put":{"operationId":"updateClient","summary":"Update client","description":"Update client configuration","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateClientDto"}}}},"responses":{"200":{"description":"Client updated"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]},"delete":{"operationId":"deleteClient","summary":"Delete client","description":"Permanently delete an API client","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Client deleted"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients/{id}/client-secrets":{"post":{"operationId":"generateClientSecret","summary":"Generate client secret","description":"Create a new client secret for API authentication","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateClientSecretDto"}}}},"responses":{"201":{"description":"Client secret created","content":{"application/json":{"schema":{"example":{"success":true,"secret":{"id":"secret-456","clientId":"client-123","secret":"sk_live_abc123...","environment":"production","createdAt":"2025-11-01T00:00:00Z"}}}}}}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]},"get":{"operationId":"listClientSecrets","summary":"List client secrets","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients/{id}/client-secrets/{environment}":{"get":{"operationId":"getClientSecret","summary":"Get client secret by environment","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"environment","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]},"delete":{"operationId":"revokeClientSecret","summary":"Revoke client secret","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"environment","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients/{id}/client-secrets/{environment}/rotate":{"post":{"operationId":"rotateClientSecret","summary":"Rotate client secret","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"environment","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients/{clientId}/webhooks":{"post":{"operationId":"registerWebhook","summary":"Register webhook","description":"Register a webhook endpoint to receive event notifications","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWebhookDto"}}}},"responses":{"201":{"description":"Webhook registered","content":{"application/json":{"schema":{"example":{"success":true,"webhook":{"id":"webhook-789","url":"https://myapp.com/webhooks/billbutler","events":["bill.created","bill.updated"],"environment":"production","enabled":true,"secret":"whsec_abc123..."}}}}}}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]},"get":{"operationId":"getWebhooks","summary":"Get webhooks for client","description":"List all registered webhooks","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"List of webhooks","content":{"application/json":{"schema":{"example":{"webhooks":[{"id":"webhook-789","url":"https://myapp.com/webhooks","events":["bill.created"],"environment":"production"}]}}}}}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients/{clientId}/client-secrets/{environment}/webhook":{"put":{"operationId":"updateWebhookConfiguration","summary":"Update webhook configuration","description":"Update webhook URL and events for an environment","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}},{"name":"environment","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookDto"}}}},"responses":{"200":{"description":"Webhook updated"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]},"get":{"operationId":"getWebhookConfiguration","summary":"Get webhook configuration","description":"Retrieve webhook details for an environment","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}},{"name":"environment","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook details"},"404":{"description":"Webhook not found"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients/{clientId}/webhooks/{webhookId}":{"delete":{"operationId":"deleteWebhook","summary":"Delete webhook subscription","description":"Removes a webhook subscription. Emits a webhook_endpoint.deleted activity event for the audit feed.","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}},{"name":"webhookId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook deleted","content":{"application/json":{"schema":{"example":{"success":true,"message":"Webhook deleted","webhook_id":"webhook-789"}}}}},"404":{"description":"Webhook not found"}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/iam/clients/{clientId}/webhooks/{webhookId}/test":{"post":{"operationId":"testWebhook","summary":"Send a test webhook delivery","description":"POSTs a signed synthetic event to the registered webhook URL. The event_type must be included in the subscription.","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}},{"name":"webhookId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestWebhookDto"}}}},"responses":{"200":{"description":"Delivery attempt finished (check success for HTTP 2xx from endpoint)","content":{"application/json":{"schema":{"example":{"success":true,"status_code":200,"delivery_id":"evt_test_abc123","message":"Test webhook delivered"}}}}}},"tags":["Authentication"],"security":[{"jwt-auth":[]}]}},"/internal-iam/auth/signup":{"post":{"operationId":"signUp","summary":"Worker signup","description":"Register a new worker account with email and password","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"Worker registered successfully","content":{"application/json":{"schema":{"example":{"success":true,"workerId":"worker_abc123","email":"worker@example.com","message":"Worker registered successfully","error":""}}}}},"400":{"description":"Invalid input or registration failed"}},"tags":["IAM Management"]}},"/internal-iam/auth/confirm-signup":{"post":{"operationId":"confirmSignUp","summary":"Confirm worker signup","description":"Confirm signup with the verification code sent to the worker email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmSignUpDto"}}}},"responses":{"200":{"description":"Signup confirmed; worker can now sign in","content":{"application/json":{"schema":{"example":{"success":true,"worker_id":"worker_abc123","email":"worker@example.com","message":"Account confirmed. You can now sign in.","error":""}}}}},"400":{"description":"Invalid code or confirmation failed"}},"tags":["IAM Management"]}},"/internal-iam/auth/signin":{"post":{"operationId":"signIn","summary":"Worker signin","description":"Authenticate a worker and receive JWT tokens","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInDto"}}}},"responses":{"200":{"description":"Worker authenticated successfully","content":{"application/json":{"schema":{"example":{"success":true,"data":{"tokens":{"accessToken":"eyJhbGci...","idToken":"eyJhbGci...","refreshToken":"eyJhbGci...","expiresIn":3600},"user":{"id":"worker_abc123","email":"worker@example.com","givenName":"John","familyName":"Doe"}}}}}}},"401":{"description":"Authentication failed"}},"tags":["IAM Management"]}},"/internal-iam/auth/forgot-password":{"post":{"operationId":"forgotPassword","summary":"Request password reset","description":"Send password reset code to worker email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordDto"}}}},"responses":{"200":{"description":"Reset code sent"},"400":{"description":"Request failed"}},"tags":["IAM Management"]}},"/internal-iam/auth/reset-password":{"post":{"operationId":"resetPassword","summary":"Reset password with code","description":"Set new password using the reset code sent to email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordDto"}}}},"responses":{"200":{"description":"Password reset successfully"},"400":{"description":"Invalid or expired code"}},"tags":["IAM Management"]}},"/internal-iam/auth/signout":{"post":{"operationId":"signOut","summary":"Worker signout","description":"Clear authentication cookies","parameters":[],"responses":{"200":{"description":"Signed out successfully"}},"tags":["IAM Management"]}},"/internal-iam/auth/refresh-token":{"post":{"operationId":"refreshTokenWorker","summary":"Refresh worker access token","description":"Exchange the bb_refresh_token cookie for a fresh access token via Cognito REFRESH_TOKEN_AUTH against the worker pool. Returns 401 on failure so the FE can clear cookies and redirect to signin.","parameters":[],"responses":{"200":{"description":"New access token issued"},"401":{"description":"Refresh token missing, invalid, or expired"}},"tags":["IAM Management"]}},"/bills/get":{"get":{"operationId":"getBills","summary":"Get bills for account link","description":"Retrieve bills filtered by account link and date range. Requires client credentials.","parameters":[{"name":"account_link_id","required":true,"in":"query","description":"Account link ID","schema":{"example":"link-456","type":"string"}},{"name":"start_date","required":false,"in":"query","description":"Start date (ISO format)","schema":{"example":"2025-01-01","type":"string"}},{"name":"end_date","required":false,"in":"query","description":"End date (ISO format)","schema":{"example":"2025-12-31","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of bills to return (1-500)","schema":{"example":100,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Pagination cursor","schema":{"example":"cursor_abc123","type":"string"}}],"responses":{"200":{"description":"List of bills","content":{"application/json":{"schema":{"example":{"bills":[{"id":"bill-123","billerName":"Electric Company","amount":150.5,"dueDate":"2025-12-01","status":"PENDING"}]}}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills":{"get":{"operationId":"listBillsV1","summary":"List bills for account link","description":"Versioned RESTful alias of GET /bills/get. Retrieve bills filtered by account link and date range. Requires client credentials.","parameters":[{"name":"account_link_id","required":true,"in":"query","description":"Account link ID","schema":{"example":"link-456","type":"string"}},{"name":"start_date","required":false,"in":"query","description":"Start date (ISO format)","schema":{"example":"2025-01-01","type":"string"}},{"name":"end_date","required":false,"in":"query","description":"End date (ISO format)","schema":{"example":"2025-12-31","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of bills to return (1-500)","schema":{"example":100,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Pagination cursor","schema":{"example":"cursor_abc123","type":"string"}}],"responses":{"200":{"description":"List of bills"}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/sync/trigger":{"post":{"operationId":"triggerBillSync","summary":"Trigger bill sync","description":"Initiate a bill synchronization for an account link","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerBillSyncDto"}}}},"responses":{"202":{"description":"Sync initiated","content":{"application/json":{"schema":{"example":{"syncId":"sync-789","status":"INITIATED","account_link_id":"link-456"}}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/backfill-payment-matches":{"post":{"operationId":"backfillBillPaymentMatches","summary":"Backfill bill-paid signals","description":"One-shot replay of historical ObservedPayments + Statements through the matcher pipeline for an accountLinkId. Idempotent — safe to re-run.","parameters":[],"responses":{"200":{"description":"Backfill complete","content":{"application/json":{"schema":{"example":{"success":true,"observed_payments_processed":24,"observed_payments_applied":18,"statements_processed":6,"statement_credits_applied":3}}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/{id}/transitions":{"get":{"operationId":"listBillTransitions","summary":"List bill audit transitions","description":"Returns the append-only timeline of every status flip on a bill, with the signal that drove each transition and an evidence reference (payment_id / statement_id / attempt_id).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Transition list","content":{"application/json":{"schema":{"example":{"transitions":[{"transition_id":"...","bill_id":"b-1","from_status":"PENDING","to_status":"PAID","signal":"OBSERVED_PAYMENT","paid_reason":"OBSERVED_PAYMENT","evidence_payment_id":"op-1","occurred_at":"2026-05-29T14:00:00Z"}],"next_cursor":"","has_more":false}}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/links/{linkId}/accounts/{accountId}/outstanding":{"get":{"operationId":"getAccountOutstandingSummary","summary":"Get carry-over-safe outstanding balance for an account","description":"Anchors on the latest fresh Statement; falls back to sum-of-unpaid when no fresh statement exists. Avoids double-counting prior-balance carry-over on utility-style billers.","parameters":[{"name":"linkId","required":true,"in":"path","schema":{"type":"string"}},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Outstanding summary","content":{"application/json":{"schema":{"example":{"outstanding_balance":170,"currency":"USD","as_of_statement_date":"2026-04-15","freshness":"fresh","freshness_hint_reason":"","freshness_hint_action":"","freshness_hint_retry_after":0}}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/sync":{"get":{"operationId":"getSyncStatus","summary":"Get sync status for account link","description":"Check the synchronization status for an account link","parameters":[{"name":"account_link_id","required":true,"in":"query","description":"Account link ID to sync","schema":{"example":"link-456","type":"string"}},{"name":"since","required":false,"in":"query","description":"Sync bills since this date","schema":{"example":"2025-01-01T00:00:00Z","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of bills to sync","schema":{"example":100,"type":"number"}},{"name":"include_deleted","required":false,"in":"query","description":"Include deleted bills","schema":{"example":false,"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"example":{"account_link_id":"link-456","status":"COMPLETED","billsFound":5,"completedAt":"2025-11-01T12:00:00Z"}}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/accounts":{"get":{"operationId":"getAccounts","summary":"Get connected accounts","description":"Retrieve all connected accounts for the authenticated client","parameters":[],"responses":{"200":{"description":"Connected accounts"}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/{id}/statement":{"get":{"operationId":"getStatement","summary":"Get the statement for a bill","description":"Returns the structured statement (period, balance breakdown, line items, biller metadata) for the bill. Returns 425 STATEMENT_NOT_EXTRACTED when extraction has not yet succeeded — call POST /:id/statement/refresh to trigger.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Statement payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatementDto"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}},"404":{"description":"Bill not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}},"425":{"description":"Statement not yet extracted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/{id}/statement/refresh":{"post":{"operationId":"refreshStatement","summary":"Refresh a bill statement","description":"Triggers asynchronous statement extraction for the bill. Returns 202 immediately with a request_id; clients poll the request_id for completion (status endpoint TBD in a follow-up PR) or subscribe to bill.statement.refreshed webhooks. Idempotent: identical Idempotency-Key headers within 5 minutes return the same in-flight request_id.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshStatementBodyDto"}}}},"responses":{"202":{"description":"Refresh accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshStatementResponseDto"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}},"404":{"description":"Bill not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}},"422":{"description":"Biller does not support refresh","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/{id}/statement/refresh/{request_id}":{"get":{"operationId":"getRefreshStatus","summary":"Get refresh status by request_id","description":"Returns the lifecycle of a refresh dispatched via POST /:id/statement/refresh. Status is \"pending\" until the extraction worker completes; then \"complete\" or \"failed\". Records expire 24h after creation. Cross-tenant lookups return 404 (no existence leak).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"request_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Refresh request status"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}},"404":{"description":"Request not found or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/bills/webhooks/test-events":{"post":{"operationId":"fireTestWebhookEvent","summary":"Fire a synthetic webhook event (test-only)","description":"Publishes a synthetic bill.paid / bill.partially_paid / bill.status_reverted event to the caller's registered webhook URL through the standard delivery pipeline (signing, retries, subscription filter).","parameters":[],"responses":{"202":{"description":"Event published","content":{"application/json":{"schema":{"example":{"event_id":"evt_synth_1234","event_type":"bill.paid"}}}}}},"tags":["Bills"],"security":[{"client-api-key":[]}]}},"/v1/insights":{"get":{"operationId":"listInsights","summary":"List insights for an account","description":"Returns insights for a specific account (account_id) under one of your biller connections (account_link_id), newest first. Both account_link_id and account_id are required. Optionally filter by user_aid and status. Cursor-paginated.","parameters":[{"name":"account_link_id","required":true,"in":"query","description":"The account_link_id (biller connection) to list insights for.","schema":{"example":"alink_01HX...","type":"string"}},{"name":"account_id","required":true,"in":"query","description":"The account_id (the account within the connection) to list insights for.","schema":{"example":"acct_01HX...","type":"string"}},{"name":"user_aid","required":false,"in":"query","description":"Optional filter to insights for a specific end-user (your client_user_id).","schema":{"example":"user_42","type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by lifecycle status.","schema":{"example":"OPEN","enum":["OPEN","SNOOZED","DISMISSED","RESOLVED"],"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of insights to return (1-100).","schema":{"example":50,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Opaque pagination cursor from a prior response.","schema":{"example":"cursor_abc123","type":"string"}}],"responses":{"200":{"description":"List of insights","content":{"application/json":{"schema":{"example":{"insights":[{"id":"insight_01HX5...","user_aid":"user_42","account_link_id":"alink_01HX...","account_id":"acct_01HX...","biller_id":"test_electric_company","bill_id":"bill_01HX...","type":"duplicate_charge","severity":"warn","status":"OPEN","evidence":{},"proposed_actions":[{"action_type":"review_bill","label":"Review bill","params":{}}],"reasoning":"Two charges of the same amount within 24h.","created_at":"2026-06-13T12:00:00.000Z","snoozed_until":null,"resolved_at":null}],"has_more":false,"next_cursor":""}}}}}},"tags":["Insights"],"security":[{"client-api-key":[]}]}},"/v1/insights/{id}":{"get":{"operationId":"getInsightById","summary":"Get an insight by id","description":"Returns a single insight with full evidence, proposed actions, and reasoning. Cross-tenant lookups return 404 (no existence leak).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"user_aid","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Insight detail"},"404":{"description":"Insight not found"}},"tags":["Insights"],"security":[{"client-api-key":[]}]}},"/v1/insights/{id}/snooze":{"post":{"operationId":"snoozeInsight","summary":"Snooze an insight","description":"Snooze the insight until a future instant. Idempotent on the same `until`.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"user_aid","required":true,"in":"query","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnoozeInsightBodyDto"}}}},"responses":{"200":{"description":"Snoozed insight"},"404":{"description":"Insight not found"}},"tags":["Insights"],"security":[{"client-api-key":[]}]}},"/v1/insights/{id}/dismiss":{"post":{"operationId":"dismissInsight","summary":"Dismiss an insight","description":"Dismiss the insight (terminal). Idempotent once dismissed.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"user_aid","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Dismissed insight"},"404":{"description":"Insight not found"}},"tags":["Insights"],"security":[{"client-api-key":[]}]}},"/v1/insights/{id}/resolve":{"post":{"operationId":"resolveInsight","summary":"Resolve an insight","description":"Resolve the insight. Terminal after a dismiss (the user action wins).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"user_aid","required":true,"in":"query","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveInsightBodyDto"}}}},"responses":{"200":{"description":"Resolved insight"},"404":{"description":"Insight not found"}},"tags":["Insights"],"security":[{"client-api-key":[]}]}},"/billers":{"get":{"operationId":"listBillersV1","summary":"List billers","description":"Versioned alias of GET /billers. Retrieve list of available billers with optional filtering. Requires client credentials.","parameters":[{"name":"search_term","required":false,"in":"query","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","schema":{"type":"string"}},{"name":"consent_scope","required":false,"in":"query","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"default":1,"type":"number"}},{"name":"page_size","required":false,"in":"query","schema":{"default":100,"type":"number"}},{"name":"sort_by","required":false,"in":"query","schema":{"type":"string"}},{"name":"sort_order","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"List of billers"}},"tags":["Billers"],"security":[{"client-api-key":[]}]}},"/billers/{id}":{"get":{"operationId":"getBillerById","summary":"Get biller by ID","description":"Retrieve detailed information for a specific biller","parameters":[{"name":"id","required":true,"in":"path","description":"Biller ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Biller details"},"404":{"description":"Biller not found"}},"tags":["Billers"],"security":[{"client-api-key":[]}]},"patch":{"operationId":"updateBillerType","summary":"Correct an agent-classified biller type","description":"Updates the biller_type on an existing BillerConfig. Only biller_type is settable from this public endpoint; full editing is operator-only.","parameters":[{"name":"id","required":true,"in":"path","description":"BillerConfig ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBillerTypeDto"}}}},"responses":{"200":{"description":"Biller updated"},"400":{"description":"Invalid biller_type"},"404":{"description":"Biller not found"}},"tags":["Billers"],"security":[{"client-api-key":[]}]}},"/billers/search/by-email":{"get":{"operationId":"searchBillersByEmail","summary":"Search billers by email domain","description":"Find billers matching an email domain","parameters":[{"name":"email","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Matching billers"}},"tags":["Billers"],"security":[{"client-api-key":[]}]}},"/billers/login":{"post":{"operationId":"loginToBillerV1","summary":"Login to biller","description":"Versioned alias of POST /billers/login. Authenticate with a biller website and create a session. Requires client credentials.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginToBillerDto"}}}},"responses":{"200":{"description":"Login successful"}},"tags":["Billers"],"security":[{"client-api-key":[]}]}},"/billers/discover-accounts":{"post":{"operationId":"discoverBillerAccounts","summary":"Discover biller accounts","description":"Discover available accounts from a biller","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverBillerAccountsDto"}}}},"responses":{"200":{"description":"Accounts discovered","content":{"application/json":{"schema":{"example":{"accounts":[{"account_id":"acc_123","account_number":"****1234","account_name":"Primary Account","account_type":"ELECTRIC","balance":150.5,"currency":"USD"}],"biller_id":"duke_energy","user_id":"user_456","message":"Accounts discovered successfully","success":true}}}}}},"tags":["Billers"],"security":[{"client-api-key":[]}]}},"/billers/onboarding":{"post":{"operationId":"requestBillerOnboarding","summary":"Request biller onboarding","description":"Request onboarding for a new biller. Requires client credentials.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestBillerOnboardingDto"}}}},"responses":{"201":{"description":"Onboarding request created"}},"tags":["Billers"],"security":[{"client-api-key":[]}]}},"/billers/user-initiated-onboarding-requests":{"post":{"operationId":"requestUserInitiatedBillerOnboarding","summary":"Request user-initiated biller onboarding","description":"Submit a biller that BillEBox discovered in-product but is not yet in the catalog. URL is SSRF-checked and normalized server-side. Server mints the onboarding_request_id.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInitiatedBillerOnboardingRequestDto"}}}},"responses":{"202":{"description":"Onboarding request accepted"},"400":{"description":"Invalid DTO or malformed URL"},"403":{"description":"URL resolved to a private/loopback/link-local IP"}},"tags":["Billers"],"security":[{"client-api-key":[]}]}},"/billers/onboarding/{id}":{"get":{"operationId":"getBillerOnboardingRequest","summary":"Get biller onboarding request","description":"Get details of a biller onboarding request. Requires client credentials.","parameters":[{"name":"id","required":true,"in":"path","description":"Onboarding request ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Onboarding request details"}},"tags":["Billers"],"security":[{"client-api-key":[]}]}},"/v1/billers/{id}":{"get":{"operationId":"getById","summary":"Get biller by id (global catalog)","description":"Returns the biller catalog entry (name, type, status, capability flags). Authenticated portal users may view any catalog biller; per-client scoping lives in the activity feed, not the entity itself. 404 if unknown id.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Biller entity."},"401":{"description":"No portal session (NEEDS_RESIGNIN)."},"404":{"description":"Biller not found (BILLER_NOT_FOUND)."}},"tags":["Billers"]}},"/emails/messages/{messageId}/attachments/{attachmentId}/download-url":{"get":{"operationId":"getAttachmentDownloadUrl","summary":"Get presigned download URL for email attachment","parameters":[{"name":"messageId","required":true,"in":"path","schema":{"type":"string"}},{"name":"attachmentId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns presigned download URL"},"404":{"description":"Message or attachment not found"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/emails":{"get":{"operationId":"getMessages","summary":"Get email messages for client","parameters":[],"responses":{"200":{"description":"Returns paginated email messages"},"401":{"description":"Unauthorized - invalid client credentials"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]},"post":{"operationId":"createMessage","summary":"Create a new email message","parameters":[],"responses":{"201":{"description":"Message created successfully"},"400":{"description":"Invalid message data"},"401":{"description":"Unauthorized - invalid client credentials"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/emails/gmail/oauth-url":{"post":{"operationId":"getGmailOAuthUrl","summary":"Get Gmail OAuth authorization URL (no auth required)","parameters":[],"responses":{"200":{"description":"Returns OAuth URL for Gmail authorization"}},"tags":["Email Discovery"]}},"/emails/gmail/oauth-callback":{"get":{"operationId":"handleGmailOAuthCallback","summary":"Handle Gmail OAuth callback (no auth required)","parameters":[],"responses":{"200":{"description":"OAuth callback handled successfully"}},"tags":["Email Discovery"]}},"/emails/gmail/connection/{connectionId}":{"delete":{"operationId":"disconnectGmailConnection","summary":"Disconnect a Gmail connection","parameters":[{"name":"connectionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Gmail connection disconnected"},"401":{"description":"Unauthorized - invalid client credentials"},"404":{"description":"Connection not found"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]},"get":{"operationId":"getGmailConnection","summary":"Get Gmail connection details","parameters":[{"name":"connectionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns Gmail connection details"},"401":{"description":"Unauthorized - invalid client credentials"},"404":{"description":"Connection not found"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/emails/gmail/pubsub-push":{"post":{"operationId":"handleGmailPubSubPush","summary":"Receive Gmail Pub/Sub push notification (Google Cloud → BillerAPI)","parameters":[{"name":"token","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Notification processed"},"401":{"description":"Invalid push token"}},"tags":["Email Discovery"]}},"/emails/{messageId}":{"get":{"operationId":"getMessageById","summary":"Get email message by ID","parameters":[{"name":"messageId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns message details"},"401":{"description":"Unauthorized - invalid client credentials"},"404":{"description":"Message not found"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/emails/classify":{"post":{"operationId":"classifyEmail","summary":"Classify an email message","parameters":[],"responses":{"200":{"description":"Email classification started successfully"},"401":{"description":"Unauthorized - invalid client credentials"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/emails/classification/{classificationId}":{"get":{"operationId":"getClassification","summary":"Get classification result by ID","parameters":[{"name":"classificationId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns classification details"},"401":{"description":"Unauthorized - invalid client credentials"},"404":{"description":"Classification not found"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/emails/find-biller":{"post":{"operationId":"findBillerByEmail","summary":"Find biller by email sender","parameters":[],"responses":{"200":{"description":"Returns biller information if found"},"401":{"description":"Unauthorized - invalid client credentials"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/emails/gmail/connect":{"post":{"operationId":"createGmailConnection","summary":"Create Gmail connection","parameters":[],"responses":{"200":{"description":"Gmail connection created successfully"},"401":{"description":"Unauthorized - invalid client credentials"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/emails/workflows":{"get":{"operationId":"listWorkflows","summary":"List email processing workflows","parameters":[],"responses":{"200":{"description":"Returns list of workflows"},"401":{"description":"Unauthorized - invalid client credentials"}},"tags":["Email Discovery"],"security":[{"client-api-key":[]}]}},"/metering/api-calls":{"get":{"operationId":"getApiCallRecords","summary":"Get API call records for client","description":"Retrieve API usage metrics for billing and monitoring. Requires client credentials.","parameters":[{"name":"client_id","required":true,"in":"query","description":"Client ID","schema":{"example":"test-client-123","type":"string"}},{"name":"start_date","required":false,"in":"query","description":"Start date (ISO format)","schema":{"example":"2025-01-01","type":"string"}},{"name":"end_date","required":false,"in":"query","description":"End date (ISO format)","schema":{"example":"2025-12-31","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of records to return","schema":{"example":"100","type":"string"}},{"name":"include_portal_noise","required":false,"in":"query","description":"When 'true', include portal-browsing requests (session-mode 2xx GETs) in the response. Default 'false' hides them. Errors and integration calls are always shown regardless.","schema":{"example":"false","enum":["true","false"],"type":"string"}},{"name":"env","required":false,"in":"query","description":"DEPRECATED — value ignored. Removal scheduled 2026-08-23.","deprecated":true,"schema":{"type":"string"}},{"name":"environment","required":false,"in":"query","description":"DEPRECATED — value ignored. Removal scheduled 2026-08-23.","deprecated":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"example":{"records":[{"endpoint":"/bills","method":"GET","statusCode":200,"responseTime":125,"timestamp":"2025-11-01T12:00:00Z"}],"totalCalls":1234}}}}}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/metering/webhook-deliveries":{"get":{"operationId":"getWebhookDeliveryRecords","summary":"Get webhook delivery records","description":"Track webhook delivery success rates and history","parameters":[{"name":"webhook_url","required":false,"in":"query","description":"Webhook URL to filter delivery records","schema":{"example":"https://example.com/webhooks","type":"string"}},{"name":"client_id","required":false,"in":"query","description":"Client ID","schema":{"example":"test-client-123","type":"string"}},{"name":"start_date","required":false,"in":"query","description":"Start date (ISO format)","schema":{"example":"2025-01-01","type":"string"}},{"name":"end_date","required":false,"in":"query","description":"End date (ISO format)","schema":{"example":"2025-12-31","type":"string"}},{"name":"event_type","required":false,"in":"query","description":"Event type filter","schema":{"example":"bill.created","type":"string"}},{"name":"delivery_status","required":false,"in":"query","description":"Delivery status filter","schema":{"example":"delivered","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max records to return","schema":{"example":50,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"example":{"records":[{"id":"wd_01HXA7C2F3K9M4P8Q","client_id":"client-123","webhook_url":"https://example.com/hook","event_type":"bill.created","status_code":200,"delivery_status":"SUCCESS","timestamp":"2025-11-01T12:00:00Z","attempt_number":1,"max_attempts":3,"delivery_group_id":"dg_01HXA7C2F3K9M4P8Q","latency_ms":142}]}}}}}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/metering/accounts/active":{"get":{"operationId":"getActiveAccountCount","summary":"Get active account count","description":"Get the number of active account links for billing purposes","parameters":[{"name":"client_id","required":true,"in":"query","description":"Client ID","schema":{"example":"test-client-123","type":"string"}},{"name":"date","required":false,"in":"query","description":"Date to check active accounts (ISO format)","schema":{"example":"2025-11-01","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"example":{"clientId":"test-client-123","activeAccounts":15,"date":"2025-11-01"}}}}}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/metering/accounts/snapshot":{"get":{"operationId":"getAccountSnapshot","summary":"Get account snapshot","description":"Historical snapshot of account counts for a specific date","parameters":[{"name":"client_id","required":true,"in":"query","description":"Client ID","schema":{"example":"test-client-123","type":"string"}},{"name":"date","required":false,"in":"query","description":"Snapshot date (ISO format)","schema":{"example":"2025-11-01","type":"string"}}],"responses":{"200":{"description":"Account snapshot data"}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/metering/consumption-usage":{"get":{"operationId":"getConsumptionUsage","summary":"Get consumption usage (bill fetch / bill pay counts)","description":"Get bill fetch and bill pay counts for a client in a date range for billing","parameters":[{"name":"clientId","required":true,"in":"query","schema":{"type":"string"}},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"}},{"name":"endDate","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"example":{"success":true,"bill_fetch_count":100,"bill_pay_count":25,"message":"Consumption usage retrieved successfully"}}}}}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/metering/api-calls/{id}":{"get":{"operationId":"getApiCallRecordById","summary":"Get single API call record with full payload detail","description":"Retrieve a single API call record including request/response payloads. Used for expandable log detail view.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"clientId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"API call record with payloads"},"404":{"description":"Record not found"}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/metering/api-calls/{id}/with-relations":{"get":{"operationId":"getApiCallRecordWithRelations","summary":"Get API call with its webhook deliveries and related requests","description":"Composite drilldown payload for the /logs detail drawer. Returns the request plus every webhook delivery triggered by its correlation_id plus every request sharing its idempotency_key (newest first, capped at 20).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"clientId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"API call with relations"},"404":{"description":"Record not found"}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/metering/account-link-events":{"get":{"operationId":"getAccountLinkRecords","summary":"Get account link events","description":"List account link/unlink events for the Events timeline","parameters":[{"name":"client_id","required":true,"in":"query","description":"Client ID","schema":{"example":"test-client-123","type":"string"}},{"name":"start_date","required":false,"in":"query","description":"Start date (ISO format)","schema":{"example":"2025-01-01","type":"string"}},{"name":"end_date","required":false,"in":"query","description":"End date (ISO format)","schema":{"example":"2025-12-31","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max records to return","schema":{"example":"100","type":"string"}}],"responses":{"200":{"description":"Account link events"}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/metering/consumption-events":{"get":{"operationId":"getConsumptionRecords","summary":"Get consumption events (bill fetches, bill payments)","description":"List individual consumption events for the Events timeline","parameters":[{"name":"client_id","required":true,"in":"query","description":"Client ID","schema":{"example":"test-client-123","type":"string"}},{"name":"start_date","required":false,"in":"query","description":"Start date (ISO format)","schema":{"example":"2025-01-01","type":"string"}},{"name":"end_date","required":false,"in":"query","description":"End date (ISO format)","schema":{"example":"2025-12-31","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max records to return","schema":{"example":"100","type":"string"}}],"responses":{"200":{"description":"Consumption events"}},"tags":["Metering"],"security":[{"client-api-key":[]}]}},"/billing/webhooks/stripe":{"post":{"operationId":"handleWebhook","summary":"Stripe webhook endpoint for billing events","parameters":[{"name":"stripe-signature","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook processed successfully"},"400":{"description":"Invalid signature or payload"}},"tags":["Webhooks"]}},"/v1/webhook_endpoints":{"post":{"operationId":"create","summary":"Create a webhook endpoint","description":"Registers a new webhook endpoint for the authenticated environment. Returns the whsec_ signing secret ONCE in the response — it is never shown again (rotate to get a new one). N endpoints per environment are supported; each has its own event filter and secret.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookEndpointDto"}}}},"responses":{"201":{"description":"Endpoint created; secret returned once."},"401":{"description":"Missing/invalid client credentials."}},"tags":["Webhook Endpoints"],"security":[{"client-api-key":[]}]},"get":{"operationId":"list","summary":"List webhook endpoints","description":"Lists every webhook endpoint registered for the authenticated environment. Signing secrets are NEVER included in list responses.","parameters":[],"responses":{"200":{"description":"List of endpoints (no secrets)."}},"tags":["Webhook Endpoints"],"security":[{"client-api-key":[]}]}},"/v1/webhook_endpoints/{id}":{"get":{"operationId":"getOne","summary":"Get a webhook endpoint","description":"Fetches a single webhook endpoint by id. No signing secret in the response.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint (no secret)."},"404":{"description":"Unknown or cross-client id (WEBHOOK_ENDPOINT_NOT_FOUND)."}},"tags":["Webhook Endpoints"],"security":[{"client-api-key":[]}]},"patch":{"operationId":"update","summary":"Update a webhook endpoint","description":"Updates a webhook endpoint (url, events filter, enabled state, description). Only supplied fields change. No secret in the response.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookEndpointDto"}}}},"responses":{"200":{"description":"Updated endpoint (no secret)."},"404":{"description":"Unknown or cross-client id (WEBHOOK_ENDPOINT_NOT_FOUND)."}},"tags":["Webhook Endpoints"],"security":[{"client-api-key":[]}]},"delete":{"operationId":"remove","summary":"Delete a webhook endpoint","description":"Removes a webhook endpoint. Idempotent from the caller's view (404 on unknown id).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Endpoint deleted."},"404":{"description":"Unknown or cross-client id (WEBHOOK_ENDPOINT_NOT_FOUND)."}},"tags":["Webhook Endpoints"],"security":[{"client-api-key":[]}]}},"/v1/webhook_endpoints/{id}/rotate-secret":{"post":{"operationId":"rotateSecret","summary":"Rotate a webhook endpoint signing secret","description":"Generates a NEW whsec_ signing secret for the endpoint and returns it ONCE. The previous secret stays valid for a 24h grace window — during that window deliveries are signed with BOTH secrets, so a consumer can roll its verification over to the new secret without dropped events. After the window the old secret stops verifying.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Rotated; new secret returned once, previous_secret_expires_at is the grace deadline."},"404":{"description":"Unknown or cross-client id (WEBHOOK_ENDPOINT_NOT_FOUND)."}},"tags":["Webhook Endpoints"],"security":[{"client-api-key":[]}]}},"/customers/{user_aid}/messages":{"post":{"operationId":"sendMessage","summary":"Send a message to a customer","description":"Delivers a push + in-app message to the customer. Marketing requires explicit consent; account_update defaults to opt-in. Supports Idempotency-Key header (24h window) and ?dry_run=true.","parameters":[{"name":"user_aid","required":true,"in":"path","schema":{"type":"string"}},{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}},{"name":"dry_run","required":true,"in":"query","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendCustomerMessageDto"}}}},"responses":{"200":{"description":"Suppressed (recipient on suppression list)"},"202":{"description":"Accepted for delivery"},"403":{"description":"Consent not granted (marketing)"},"422":{"description":"Content moderation flagged"},"429":{"description":"Rate limit exceeded"}},"tags":["Customer Messaging"],"security":[{"client-api-key":[]}]}},"/customers/{user_aid}/consents":{"post":{"operationId":"updateConsent","summary":"Update customer's per-category consent","parameters":[{"name":"user_aid","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomerConsentDto"}}}},"responses":{"200":{"description":""}},"tags":["Customer Messaging"],"security":[{"client-api-key":[]}]},"get":{"operationId":"getConsents","summary":"Inspect customer's per-category consent state","parameters":[{"name":"user_aid","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Customer Messaging"],"security":[{"client-api-key":[]}]}},"/customers/{user_aid}/complaints":{"post":{"operationId":"recordComplaint","summary":"Record a customer complaint about a message","parameters":[{"name":"user_aid","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordCustomerComplaintDto"}}}},"responses":{"202":{"description":"Complaint recorded; suppression added; webhook fan-out triggered."}},"tags":["Customer Messaging"],"security":[{"client-api-key":[]}]}},"/clients/me/suppressions":{"get":{"operationId":"list","summary":"List suppression rows for this client","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list"}},"tags":["Customer Messaging"],"security":[{"client-api-key":[]}]}},"/clients/me/suppressions/{user_aid}":{"delete":{"operationId":"remove","summary":"Manually remove a suppression row","parameters":[{"name":"user_aid","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Customer Messaging"],"security":[{"client-api-key":[]}]}},"/link/request-to-link/{id}":{"get":{"operationId":"getById","summary":"Get request-to-link details","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Request-to-link details"},"404":{"description":"Request-to-link not found"}},"tags":["Request To Link"],"security":[{"client-api-key":[]}]}},"/link/request-to-link/create":{"post":{"operationId":"create","summary":"Create request-to-link record","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRequestToLinkDto"}}}},"responses":{"201":{"description":"Request-to-link created"}},"tags":["Request To Link"],"security":[{"client-api-key":[]}]}},"/link/request-to-link/{id}/status":{"patch":{"operationId":"updateStatus","summary":"Update request-to-link status","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRequestToLinkStatusDto"}}}},"responses":{"200":{"description":""}},"tags":["Request To Link"],"security":[{"client-api-key":[]}]}},"/link/request-to-link/{id}/cancel":{"post":{"operationId":"cancel","summary":"Cancel request-to-link","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelRequestToLinkDto"}}}},"responses":{"201":{"description":""}},"tags":["Request To Link"],"security":[{"client-api-key":[]}]}},"/link/request-to-link/{id}/proceed":{"post":{"operationId":"proceed","summary":"Proceed request-to-link to link session","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Request To Link"],"security":[{"client-api-key":[]}]}},"/request-to-links":{"post":{"operationId":"create","summary":"Create request-to-link record","description":"RESTful alias of POST /link/request-to-link/create. Server-to-server: requires client credentials.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRequestToLinkDto"}}}},"responses":{"201":{"description":"Request-to-link created"}},"tags":["Request To Link"],"security":[{"client-api-key":[]}]}},"/link/test-sse":{"get":{"operationId":"testSSE","parameters":[],"responses":{"200":{"description":""}},"tags":["Account Linking"]}},"/link/test-put":{"put":{"operationId":"testPut","parameters":[],"responses":{"200":{"description":""}},"tags":["Account Linking"]}},"/link/token/validate":{"post":{"operationId":"validateLinkToken","summary":"Validate link token","description":"Validate a link token via gRPC","parameters":[],"responses":{"200":{"description":"Token validated successfully"},"400":{"description":"Invalid token"}},"tags":["Account Linking"]}},"/link/access-token/validate":{"post":{"operationId":"validateAccessToken","summary":"Validate access token","description":"Validate an access token via gRPC","parameters":[],"responses":{"200":{"description":"Access token validated successfully"},"400":{"description":"Invalid access token"}},"tags":["Account Linking"]}},"/link/link/{linkId}/status":{"get":{"operationId":"getLinkStatus","summary":"Get link status","description":"Get the status of a link","parameters":[{"name":"linkId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Link status retrieved successfully"},"404":{"description":"Link not found"}},"tags":["Account Linking"]}},"/link/link-token/{linkTokenId}/status":{"get":{"operationId":"getLinkTokenStatus","summary":"Get link token status","description":"Get the status of a link token","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Link token status retrieved successfully"},"404":{"description":"Link token not found"}},"tags":["Account Linking"]}},"/link/token/{token}/status":{"get":{"operationId":"getHostedSessionStatus","summary":"Poll hosted connect session status (#4768)","description":"Lightweight status poll for the hosted connect page, keyed by the opaque link-token string. Never returns the public token.","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Session status retrieved"},"404":{"description":"Unknown link token"}},"tags":["Account Linking"]}},"/link/link-token/{linkTokenId}/flow-state":{"get":{"operationId":"getFlowState","summary":"Get flow state","description":"Get the current flow state of a link token","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Flow state retrieved successfully"},"404":{"description":"Link token not found"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/billers":{"get":{"operationId":"listBillersForLinkToken","summary":"List billers for a link token","description":"Returns available billers, scoped to a valid link token. Used by the connect iframe biller picker.","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}},{"name":"popular","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"q","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Billers retrieved successfully"},"401":{"description":"Invalid or expired link token"}},"tags":["Account Linking"]}},"/link/token/{token}/embed-origins":{"get":{"operationId":"getLinkTokenEmbedOrigins","summary":"Resolve a link token's registered embed origins (#4783)","description":"Lightweight resolver for the hosted connect page's dynamic frame-ancestors CSP. Returns only the owning client's allowed_embed_origins. Fails closed to an empty list.","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Registered embed origins resolved"},"401":{"description":"Invalid or expired link token"}},"tags":["Account Linking"]}},"/link/link-token/{linkTokenId}/stream":{"options":{"operationId":"handleSSEPreflight","summary":"SSE preflight","description":"Handle CORS preflight for SSE endpoint","parameters":[],"responses":{"200":{"description":""}},"tags":["Account Linking"]},"get":{"operationId":"streamLinkTokenUpdates","summary":"Stream link token updates","description":"Establish SSE connection to receive real-time link token status updates via gRPC stream","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"SSE stream established"},"404":{"description":"Link token not found"}},"tags":["Account Linking"]}},"/link/link/{linkId}":{"get":{"operationId":"getLink","summary":"Get link","description":"Get link details by link ID","parameters":[{"name":"linkId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Link retrieved successfully"},"404":{"description":"Link not found"}},"tags":["Account Linking"]},"put":{"operationId":"updateLink","summary":"Update link","description":"Update link details","parameters":[{"name":"linkId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Link updated successfully"},"404":{"description":"Link not found"}},"tags":["Account Linking"]},"delete":{"operationId":"deleteLink","summary":"Delete link","description":"Delete a link","parameters":[{"name":"linkId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Link deleted successfully"},"404":{"description":"Link not found"}},"tags":["Account Linking"]}},"/link/token/create":{"post":{"operationId":"createLinkToken","summary":"Create link token","description":"Create a new link token for initiating account linking flow. Server-to-server: requires client credentials.","parameters":[],"responses":{"201":{"description":"Link token created successfully"},"400":{"description":"Invalid request"},"401":{"description":"Missing or invalid client credentials"},"403":{"description":"Body client_id does not match the authenticated client, or update.link_id names a Link owned by another client/user"},"404":{"description":"update.link_id names a Link that does not exist"}},"tags":["Account Linking"],"security":[{"client-api-key":[]}]}},"/link/token/{linkTokenId}/select-biller":{"post":{"operationId":"selectBiller","summary":"Select biller","description":"Select a biller for the linking flow","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Biller selected successfully"},"400":{"description":"Invalid request"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/credentials":{"put":{"operationId":"submitCredentialsPut","summary":"Submit credentials (PUT)","description":"Submit credentials using credentialId (matches frontend SDK)","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Credentials submitted successfully"},"400":{"description":"Invalid credentials"},"429":{"description":"Too many submission attempts — throttled (#3627)"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/submit-credentials":{"post":{"operationId":"submitCredentials","summary":"Submit credentials (POST)","description":"Submit user credentials for account linking using username/password","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Credentials submitted successfully"},"400":{"description":"Invalid credentials"},"429":{"description":"Too many submission attempts — throttled (#3627)"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/submit-mfa":{"post":{"operationId":"submitMfa","summary":"Submit MFA code","description":"Submit MFA verification code. Resolves credentialId + challengeId server-side from LinkToken + PendingMfa; the iframe only sends the user-typed OTP.","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"MFA code submitted successfully"},"400":{"description":"Invalid request body"},"404":{"description":"LinkToken not found"},"409":{"description":"LinkToken in wrong state or no active MFA challenge"},"422":{"description":"PendingMfa carries no usable challenge"},"429":{"description":"Too many submission attempts — throttled (#3627)"},"502":{"description":"Vault upstream failed to record submission"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/resend-mfa":{"post":{"operationId":"resendMfa","summary":"Resend MFA code","description":"Request a fresh MFA code. Re-drives the biller login server-side; the iframe only sends the link token in the path.","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Fresh MFA code requested"},"400":{"description":"Invalid request"},"404":{"description":"LinkToken not found"},"409":{"description":"LinkToken not awaiting an MFA challenge"},"429":{"description":"Resend rate limit exceeded"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/discover-accounts":{"post":{"operationId":"discoverAccounts","summary":"Discover accounts","description":"Trigger account discovery for the link token","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Accounts discovered successfully"},"400":{"description":"Failed to discover accounts"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/select-accounts":{"post":{"operationId":"selectAccounts","summary":"Select accounts","description":"Select accounts to link and complete the flow","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Accounts selected successfully"},"400":{"description":"Invalid account selection"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/background":{"post":{"operationId":"backgroundLinkToken","summary":"Continue connect flow in background (#4786)","description":"Marks the link token for backgrounded auto-completion: all discovered accounts are auto-selected and the public token is exchanged server-side.","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"Link token backgrounded"},"400":{"description":"Link token not eligible for backgrounding"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/exit":{"post":{"operationId":"reportHostedSessionExit","summary":"Report hosted connect session exit (#4768)","description":"Reports a user-initiated close/abandon of the hosted connect page. Emits the link.session_finished (exited) webhook.","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"Exit recorded"},"400":{"description":"Invalid request"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/events":{"post":{"operationId":"recordConnectEvent","summary":"Ingest a hosted connect-event for the server-side funnel (#4918)","description":"Raw-appends one connect/* taxonomy event keyed by the link token. Fire-and-forget: always returns 201, never surfaces telemetry failure to the client. Strict metadata allowlist (no PII).","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"Event accepted (fire-and-forget)"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/set-region":{"post":{"operationId":"setUserRegion","summary":"Set user region on LinkToken (#3729)","description":"Persist the user-selected region for region-gated billers.","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Region persisted"},"400":{"description":"Invalid region payload"}},"tags":["Account Linking"]}},"/link/links/{linkId}/confirm-region":{"post":{"operationId":"confirmLinkUserRegion","summary":"Confirm Link userRegion (#3729 follow-up)","description":"Affirm or override user_region on a Link; stamps regionConfirmedAt.","parameters":[{"name":"linkId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Confirmation persisted"},"404":{"description":"Link not found"}},"tags":["Account Linking"]}},"/link/token/exchange":{"post":{"operationId":"exchangePublicToken","summary":"Exchange public token","description":"Exchange a public token for an access token. Server-to-server: requires client credentials.","parameters":[],"responses":{"200":{"description":"Token exchanged successfully"},"400":{"description":"Invalid public token"},"401":{"description":"Missing or invalid client credentials"},"403":{"description":"Body client_id does not match the authenticated client"}},"tags":["Account Linking"],"security":[{"client-api-key":[]}]}},"/link/token/{linkTokenId}/complete":{"post":{"operationId":"completeFlow","summary":"Complete linking flow","description":"Explicitly complete the linking flow for a link token","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Flow completed successfully"},"400":{"description":"Invalid request or flow cannot be completed"}},"tags":["Account Linking"]}},"/link/token/{linkTokenId}/public-token":{"get":{"operationId":"getPublicToken","summary":"Get public token","description":"Get the public token for a link token","parameters":[{"name":"linkTokenId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Public token retrieved successfully"},"404":{"description":"Link token not found or public token not generated"}},"tags":["Account Linking"]}},"/link-tokens":{"post":{"operationId":"createLinkToken","summary":"Create link token","description":"RESTful alias of POST /link/token/create. Create a new link token for initiating an account linking flow. Server-to-server: requires client credentials.","parameters":[],"responses":{"201":{"description":"Link token created successfully"},"400":{"description":"Invalid request"},"401":{"description":"Missing or invalid client credentials"},"403":{"description":"Body client_id does not match the authenticated client"}},"tags":["Account Linking"],"security":[{"client-api-key":[]}]}},"/link-tokens/exchange":{"post":{"operationId":"exchangePublicToken","summary":"Exchange public token","description":"RESTful alias of POST /link/token/exchange. Exchange a public token for an access token. Server-to-server: requires client credentials.","parameters":[],"responses":{"200":{"description":"Token exchanged successfully"},"400":{"description":"Invalid public token"},"401":{"description":"Missing or invalid client credentials"},"403":{"description":"Body client_id does not match the authenticated client"}},"tags":["Account Linking"],"security":[{"client-api-key":[]}]}},"/v1/links/{id}":{"get":{"operationId":"getById","summary":"Get link by id for the authenticated client","description":"Returns the full link entity (biller, status, scopes, dates, accounts) scoped to the session client_id. 404 if the link is unknown; 403 CLIENT_ID_MISMATCH if it belongs to a different client.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Link entity."},"401":{"description":"No portal session (NEEDS_RESIGNIN)."},"403":{"description":"Link belongs to a different client (CLIENT_ID_MISMATCH)."},"404":{"description":"Link not found (LINK_NOT_FOUND)."}},"tags":["Links"]}},"/biller-portal/auth/signup":{"post":{"operationId":"signUp","summary":"Biller portal signup","description":"Register a new biller portal account with email verification","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillerSignUpDto"}}}},"responses":{"201":{"description":"Account created, verification email sent"},"400":{"description":"Invalid input"},"409":{"description":"Account already exists"}},"tags":["Biller Portal"]}},"/biller-portal/auth/confirm-signup":{"post":{"operationId":"confirmSignUp","summary":"Confirm biller portal signup","description":"Confirm account with the verification code sent to email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillerConfirmSignUpDto"}}}},"responses":{"200":{"description":"Account confirmed"},"400":{"description":"Invalid or expired code"}},"tags":["Biller Portal"]}},"/biller-portal/auth/signin":{"post":{"operationId":"signIn","summary":"Biller portal signin","description":"Authenticate and receive JWT tokens","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillerSignInDto"}}}},"responses":{"200":{"description":"Authenticated successfully","content":{"application/json":{"schema":{"example":{"success":true,"data":{"tokens":{"accessToken":"eyJhbGci...","idToken":"eyJhbGci...","refreshToken":"eyJhbGci...","expiresIn":3600},"user":{"id":"cognito-sub","email":"biller@example.com","givenName":"Jane","familyName":"Doe"}}}}}}},"401":{"description":"Invalid credentials"},"403":{"description":"Account not confirmed"}},"tags":["Biller Portal"]}},"/biller-portal/auth/signout":{"post":{"operationId":"signOut","summary":"Biller portal signout","description":"Clear authentication cookies","parameters":[],"responses":{"200":{"description":"Signed out successfully"}},"tags":["Biller Portal"]}},"/biller-portal/auth/forgot-password":{"post":{"operationId":"forgotPassword","summary":"Initiate biller portal password reset","description":"Send a password reset code to the registered email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillerForgotPasswordDto"}}}},"responses":{"200":{"description":"Password reset code sent"}},"tags":["Biller Portal"]}},"/biller-portal/auth/confirm-forgot-password":{"post":{"operationId":"confirmForgotPassword","summary":"Confirm biller portal password reset","description":"Reset password using the code sent to email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillerConfirmForgotPasswordDto"}}}},"responses":{"200":{"description":"Password reset successful"},"400":{"description":"Invalid or expired code"}},"tags":["Biller Portal"]}},"/biller-portal/auth/resend-confirmation-code":{"post":{"operationId":"resendConfirmationCode","summary":"Resend biller portal verification code","description":"Resend the email verification code for signup confirmation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillerResendCodeDto"}}}},"responses":{"200":{"description":"Verification code resent"}},"tags":["Biller Portal"]}},"/internal/agent-runner/biller-login-bundle":{"post":{"operationId":"getBillerLoginBundle","summary":"Get login-only execution bundle for post-login recording sessions","description":"Returns the biller's base_url, login_config_json, and decrypted credentials. Used by McpRecordingSession.preClaudeLogin so the agent can sign into the biller before Claude takes a post-login snapshot. Requires only login selectors to be present (the other buckets are what the recording is about to produce).","parameters":[],"responses":{"200":{"description":"Login bundle returned"},"400":{"description":"Missing creds or login selectors"},"404":{"description":"Biller not found"}},"tags":["Agent Runner Internal"]}},"/v1/activity":{"get":{"operationId":"list","summary":"List activity events for the authenticated client","description":"Returns a time-descending page of activity events scoped to the session's client_id. Filter by event_type (repeatable), resource (resource_type + resource_id), or time window. Pagination via opaque cursor. Default page size 50, max 200.","parameters":[{"name":"event_type","required":false,"in":"query","description":"Event type filter (repeatable). OR semantics across values.","schema":{"example":["webhook.delivered","webhook.delivery-failed"],"type":"array","items":{"type":"string"}}},{"name":"resource_type","required":false,"in":"query","description":"Resource type filter (e.g. \"link\", \"bill\")","schema":{"type":"string"}},{"name":"resource_id","required":false,"in":"query","description":"Resource ID filter; pairs with resource_type","schema":{"type":"string"}},{"name":"since","required":false,"in":"query","description":"ISO 8601 lower bound (inclusive)","schema":{"example":"2026-01-01T00:00:00Z","type":"string"}},{"name":"until","required":false,"in":"query","description":"ISO 8601 upper bound (exclusive)","schema":{"example":"2026-02-01T00:00:00Z","type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Opaque pagination cursor from a prior response","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size; default 50, clamped to [1, 200]","schema":{"example":50,"type":"number"}},{"name":"correlation_id","required":false,"in":"query","description":"Filter to events with this correlation_id (the x-correlation-id of the originating request).","schema":{"type":"string"}},{"name":"env","required":false,"in":"query","description":"DEPRECATED — value ignored. Removal scheduled 2026-08-23.","deprecated":true,"schema":{"type":"string"}},{"name":"environment","required":false,"in":"query","description":"DEPRECATED — value ignored. Removal scheduled 2026-08-23.","deprecated":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Page of activity events."},"401":{"description":"No portal session (NEEDS_RESIGNIN)."},"403":{"description":"client_id query did not match session (CLIENT_ID_MISMATCH)."}},"tags":["Activity"]}},"/v1/activity/{id}":{"get":{"operationId":"getById","summary":"Fetch a single activity event by id","description":"Returns the event scoped to the session's client_id. 404 when the event is not found or belongs to a different client.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Single activity event."},"401":{"description":"No portal session (NEEDS_RESIGNIN)."},"404":{"description":"Event not found for this client."}},"tags":["Activity"]}},"/v1/activity/{id}/deliveries":{"get":{"operationId":"getDeliveries","summary":"Per-event webhook delivery timeline","description":"Returns the time-ordered list of webhook delivery attempts (succeeded / failed / exhausted) that fired for this event. Joined by correlation_id. Empty array when the event has no deliveries; 404 if the source event is missing.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Delivery timeline for the event."},"401":{"description":"No portal session."},"404":{"description":"Source event not found for this client."}},"tags":["Activity"]}},"/v1/activity/{id}/related":{"get":{"operationId":"getRelated","summary":"Other activity events sharing this event's correlation_id","description":"Returns the time-ordered list of activity events that share the source event's correlation_id, minus the source itself. Empty array when the event has no correlation_id; 404 if the source is missing.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Related events for the trace."},"401":{"description":"No portal session."},"404":{"description":"Source event not found for this client."}},"tags":["Activity"]}},"/v1/activity/.csv":{"get":{"operationId":"listCsv","summary":"Export activity events as CSV","description":"Same filters as GET /v1/activity. Returns a CSV download (Content-Disposition: attachment). Capped at the same page size as the JSON endpoint — for larger windows, paginate with cursor.","parameters":[{"name":"event_type","required":false,"in":"query","description":"Event type filter (repeatable). OR semantics across values.","schema":{"example":["webhook.delivered","webhook.delivery-failed"],"type":"array","items":{"type":"string"}}},{"name":"resource_type","required":false,"in":"query","description":"Resource type filter (e.g. \"link\", \"bill\")","schema":{"type":"string"}},{"name":"resource_id","required":false,"in":"query","description":"Resource ID filter; pairs with resource_type","schema":{"type":"string"}},{"name":"since","required":false,"in":"query","description":"ISO 8601 lower bound (inclusive)","schema":{"example":"2026-01-01T00:00:00Z","type":"string"}},{"name":"until","required":false,"in":"query","description":"ISO 8601 upper bound (exclusive)","schema":{"example":"2026-02-01T00:00:00Z","type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Opaque pagination cursor from a prior response","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size; default 50, clamped to [1, 200]","schema":{"example":50,"type":"number"}},{"name":"correlation_id","required":false,"in":"query","description":"Filter to events with this correlation_id (the x-correlation-id of the originating request).","schema":{"type":"string"}},{"name":"env","required":false,"in":"query","description":"DEPRECATED — value ignored. Removal scheduled 2026-08-23.","deprecated":true,"schema":{"type":"string"}},{"name":"environment","required":false,"in":"query","description":"DEPRECATED — value ignored. Removal scheduled 2026-08-23.","deprecated":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"CSV file with one row per activity event."}},"tags":["Activity"]}},"/v1/activity/replay":{"post":{"operationId":"replay","summary":"Replay a webhook delivery from the activity feed","description":"Re-enqueue a webhook delivery for re-send. delivery_id mirrors the activity row resource_id for webhook events. Rate-limited 1/30s/delivery; 429 carries retry_after_ms.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplayActivityDto"}}}},"responses":{"201":{"description":"Replay initiated."},"422":{"description":"Delivery has no backing dead letter (not replayable)."},"429":{"description":"Rate limited; retry after retry_after_ms."}},"tags":["Activity"]}},"/portal-sse/events/{portalType}":{"get":{"operationId":"streamEvents","summary":"Stream domain events for a portal type via SSE","parameters":[{"name":"portalType","required":true,"in":"path","schema":{"enum":["admin","internal","client","biller"],"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Portal SSE"]}},"/iam/webhooks/deliveries":{"get":{"operationId":"getDeliveryRecords","summary":"Get webhook delivery records","description":"Retrieve delivery history for a client","parameters":[{"name":"client_id","required":true,"in":"query","description":"Client ID","schema":{"example":"client-123","type":"string"}},{"name":"start_date","required":false,"in":"query","description":"Start date (ISO format)","schema":{"type":"string"}},{"name":"end_date","required":false,"in":"query","description":"End date (ISO format)","schema":{"type":"string"}},{"name":"event_type","required":false,"in":"query","description":"Filter by event type","schema":{"type":"string"}},{"name":"delivery_status","required":false,"in":"query","description":"Filter by delivery status (SUCCESS/FAILED)","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max records to return","schema":{"default":50,"type":"number"}}],"responses":{"200":{"description":"Delivery records returned"}},"tags":["Webhook Deliveries"],"security":[{"jwt-auth":[]}]}},"/iam/webhooks/deliveries/dead-letters":{"get":{"operationId":"getDeadLetters","summary":"Get dead letter webhooks","description":"Retrieve webhooks that exhausted all retries","parameters":[{"name":"client_id","required":true,"in":"query","description":"Client ID","schema":{"example":"client-123","type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by status (DEAD/REPLAYED)","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max records to return","schema":{"default":20,"type":"number"}},{"name":"last_evaluated_key","required":false,"in":"query","description":"Pagination cursor","schema":{"type":"string"}}],"responses":{"200":{"description":"Dead letter records returned"}},"tags":["Webhook Deliveries"],"security":[{"jwt-auth":[]}]}},"/iam/webhooks/deliveries/dead-letters/{id}/replay":{"post":{"operationId":"replayDeadLetter","summary":"Replay a dead letter webhook","description":"Re-enqueue a dead letter for delivery","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplayWebhookDto"}}}},"responses":{"201":{"description":"Replay initiated"},"404":{"description":"Dead letter not found"}},"tags":["Webhook Deliveries"],"security":[{"jwt-auth":[]}]}},"/iam/webhooks/deliveries/{id}/replay":{"post":{"operationId":"replayDelivery","summary":"Replay a webhook delivery from /events","description":"Re-enqueue a failed delivery for re-send. Rate-limited 1/30s/delivery.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplayDeliveryDto"}}}},"responses":{"201":{"description":"Replay initiated"},"422":{"description":"Delivery has no backing dead letter (not replayable)"},"429":{"description":"Rate limited; retry after retry_after_ms"}},"tags":["Webhook Deliveries"],"security":[{"jwt-auth":[]}]}},"/iam/webhooks/deliveries/resend-failed":{"post":{"operationId":"resendFailed","summary":"Resend all failed webhook deliveries since a timestamp","description":"Re-enqueues every dead-lettered delivery with exhausted_at >= since (optionally scoped to one endpoint). Rate-limited 1/30s/endpoint; capped at 100 deliveries per invocation.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendFailedWebhooksDto"}}}},"responses":{"202":{"description":"Bulk resend accepted and enqueued"},"429":{"description":"Rate limited; retry after retry_after_ms"}},"tags":["Webhook Deliveries"],"security":[{"jwt-auth":[]}]}},"/v1/pay/token/create":{"post":{"operationId":"createPayToken","summary":"Create pay token","description":"Mint a pay_token for the hosted Elements add-payment-method / pay flows. Server-to-server: requires client credentials. Short-lived (15 min).","parameters":[],"responses":{"201":{"description":"Pay token created successfully"},"400":{"description":"Invalid request"},"401":{"description":"Missing or invalid client credentials"},"403":{"description":"Body client_id does not match the authenticated client"}},"tags":["Pay"],"security":[{"client-api-key":[]}]}},"/v1/pay/token/validate":{"post":{"operationId":"validatePayToken","summary":"Validate pay token","description":"Validate a pay_token and read its scope (status / bill / amount / payment method). Unauthenticated — the token itself is the credential.","parameters":[],"responses":{"200":{"description":"Token validated (check `valid` flag)"},"400":{"description":"Invalid request"}},"tags":["Pay"]}},"/v1/pay/token/{token}/embed-origins":{"get":{"operationId":"getPayTokenEmbedOrigins","summary":"Resolve a pay token's PCI-embed approval + registered embed origins (#4895)","description":"Lightweight resolver for the hosted /elements card pages' dynamic frame-ancestors CSP. Returns the owning client's allowed_embed_origins ONLY when elements_embed_approved is true (operator-approved, PCI). Fails closed to { elements_embed_approved: false, allowed_embed_origins: [] }.","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"PCI-embed approval + registered embed origins resolved"},"401":{"description":"Invalid or expired pay token"}},"tags":["Pay"]}},"/pay-tokens":{"post":{"operationId":"createPayToken","summary":"Create pay token","description":"RESTful alias of POST /v1/pay/token/create. Mint a pay_token for the hosted Elements add-payment-method / pay flows. Server-to-server: requires client credentials. Short-lived (15 min).","parameters":[],"responses":{"201":{"description":"Pay token created successfully"},"400":{"description":"Invalid request"},"401":{"description":"Missing or invalid client credentials"},"403":{"description":"Body client_id does not match the authenticated client"}},"tags":["Pay"],"security":[{"client-api-key":[]}]}},"/discovery-runs/{id}":{"get":{"operationId":"getDiscoveryRun","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"user_id","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/discovery-runs":{"get":{"operationId":"getLatestForConnection","parameters":[{"name":"email_connection_id","required":true,"in":"query","schema":{"type":"string"}},{"name":"user_id","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/discovery-runs/{id}/stream":{"get":{"operationId":"streamDiscoveryRun","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"user_id","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/retrieval/{accountLinkId}":{"get":{"operationId":"getRetrievalRun","parameters":[{"name":"accountLinkId","required":true,"in":"path","schema":{"type":"string"}},{"name":"user_id","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/retrieval/{accountLinkId}/stream":{"get":{"operationId":"streamRetrievalRun","parameters":[{"name":"accountLinkId","required":true,"in":"path","schema":{"type":"string"}},{"name":"user_id","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/auth/webauthn/register-options":{"post":{"operationId":"registerOptions","summary":"Generate passkey registration options","description":"Step 1 of WebAuthn enrollment. Returns the `PublicKeyCredentialCreationOptionsJSON` the browser passes to `startRegistration()`.","parameters":[],"responses":{"201":{"description":""}},"tags":["🔐 Auth - WebAuthn (step-up)"]}},"/v1/auth/webauthn/register-verify":{"post":{"operationId":"registerVerify","summary":"Verify passkey registration response","description":"Step 2 of WebAuthn enrollment.","parameters":[],"responses":{"201":{"description":""}},"tags":["🔐 Auth - WebAuthn (step-up)"]}},"/v1/auth/webauthn/authenticate-options":{"post":{"operationId":"authenticateOptions","summary":"Generate passkey step-up assertion options","parameters":[],"responses":{"201":{"description":""}},"tags":["🔐 Auth - WebAuthn (step-up)"]}},"/v1/auth/webauthn/authenticate-verify":{"post":{"operationId":"authenticateVerify","summary":"Verify passkey step-up assertion + mint step-up token","description":"Step 2 of WebAuthn step-up. On success, sets `bb_stepup_token` HttpOnly cookie scoped to `/v1/_internal/`.","parameters":[],"responses":{"201":{"description":""}},"tags":["🔐 Auth - WebAuthn (step-up)"]}},"/v1/auth/webauthn/credentials":{"get":{"operationId":"listCredentials","summary":"List registered passkeys for the current user","parameters":[],"responses":{"200":{"description":""}},"tags":["🔐 Auth - WebAuthn (step-up)"]}},"/v1/auth/webauthn/credentials/{credentialId}":{"delete":{"operationId":"revokeCredential","summary":"Revoke a single passkey","parameters":[{"name":"credentialId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["🔐 Auth - WebAuthn (step-up)"]}},"/v1/_internal/playground/proxy-call":{"post":{"operationId":"proxyCall","summary":"Forward an allowlisted REST call to the BillerAPI public surface","description":"PR8 read-only proxy. Used by the playground API explorer when env=production. Server fetches the caller's production secret and forwards the request — the browser never sees the secret.","parameters":[],"responses":{"201":{"description":""}},"tags":["🔒 Internal - Playground Proxy"]}},"/v1/_internal/playground/client-secret":{"get":{"operationId":"getClientSecret","summary":"Reveal the prod client_secret for copy-with-real-secret snippets","description":"Returns the caller's active production client_secret, gated on a fresh step-up cookie. The browser uses this to substitute the masked placeholder in copy-as-curl snippets when the user explicitly opts in via the \"Copy with real secret\" button. Each call is audited; never call without a freshly issued step-up token.","parameters":[{"name":"env","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["🔒 Internal - Playground Proxy"]}},"/v1/_internal/quickstart-progress":{"get":{"operationId":"get","summary":"Snapshot of the five Quickstart steps for the authenticated client","description":"Server-fan-out: queries metering + link gRPC in parallel and returns a single boolean-per-step result. The FE polls this and merges with localStorage so manual completions still count.","parameters":[],"responses":{"200":{"description":""}},"tags":["🔒 Internal - Playground"]}},"/sandbox/_internal/utilities/fire-webhook":{"post":{"operationId":"fireWebhook","summary":"Fire a canned webhook event to the caller (sandbox-only utility)","description":"For developers iterating on their webhook handler. Picks the caller's registered subscription if one exists; otherwise auto-registers a temporary capture session so the playground inspector picks the delivery up immediately. Never reads client_id from the body.","parameters":[],"responses":{"201":{"description":""}},"tags":["🔒 Internal - Sandbox Utilities"]}},"/sandbox/_internal/utilities/reset-session":{"post":{"operationId":"resetSession","summary":"Reset cached session state for a (biller, user) pair (forward-compat stub)","description":"Sandbox link.controller is stateless; the real session state lives in scraping-cache (#2879) and does not yet expose a clear API. Endpoint exists so the FE can ship the button; returns 501 with a hint until the cache is wired up.","parameters":[],"responses":{"201":{"description":""}},"tags":["🔒 Internal - Sandbox Utilities"]}},"/v1/auth/step-up/verify-password":{"post":{"operationId":"verifyPassword","summary":"Re-verify the operator password and mint a step-up token (fallback path)","description":"For users who haven't enrolled a passkey, or whose browser doesn't support WebAuthn. The password is re-checked against Cognito; the existing session continues — no new tokens issued.","parameters":[],"responses":{"201":{"description":""}},"tags":["🔐 Auth - Step-up"]}},"/v1/_internal/webhook-capture/sessions":{"post":{"operationId":"createSession","summary":"Mint a capture session token + URL","description":"Used by the playground webhook inspector capture-mode on mount. The returned URL is what the frontend later registers as a temporary webhook subscription. Also best-effort registers a temp subscription in webhooks-service so real BillerAPI events flow into the capture URL during the 30-min token window.","parameters":[],"responses":{"201":{"description":""}},"tags":["🔒 Internal - Webhook Capture"]}},"/v1/_internal/webhook-capture/{token}":{"post":{"operationId":"receive","summary":"Inbound capture endpoint","description":"Unauthenticated (token is the capability). Validated by HS256-JWT signature on the token itself.","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["🔒 Internal - Webhook Capture"]}},"/v1/_internal/webhook-capture/{token}/stream":{"get":{"operationId":"stream","summary":"SSE — stream captured deliveries","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["🔒 Internal - Webhook Capture"]}},"/operations/docs-feedback":{"post":{"operationId":"submitDocsFeedback","summary":"Record docs \"Was this helpful?\" feedback (public, unauthenticated)","description":"Public ingest for the docs feedback widget. No auth, no PII. Records a thumbs-up/down (and optional bounded comment) against a docs path. Rate-limited per IP. Always returns 202 on a valid body; malformed bodies return the coded 400 error envelope.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitDocsFeedbackDto"}}}},"responses":{"202":{"description":"Feedback accepted"},"400":{"description":"Malformed body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}},"429":{"description":"RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorDto"}}}}},"tags":["Docs Feedback"]}},"/v1/biller-connect/ticket":{"post":{"operationId":"mintTicket","summary":"Mint a single-use biller-connect WSS capability ticket (server-to-server)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintTicketDto"}}}},"responses":{"201":{"description":"Ticket minted"}},"tags":["Biller Connect"],"security":[{"client-api-key":[]}]}}},"info":{"title":"BillerAPI Client API","description":"REST API for external developers integrating with BillerAPI. All protected server-side endpoints require a BillerAPI API key sent as Authorization: Bearer bak_test_... for sandbox or Authorization: Bearer bak_live_... for production.","version":"1.0","contact":{}},"tags":[{"name":"Authentication","description":"User authentication and session management"},{"name":"Bills","description":"Bill retrieval and synchronization"},{"name":"Billers","description":"Biller discovery and account linking"},{"name":"Email Discovery","description":"Email-based bill discovery"},{"name":"Account Linking","description":"Account linking and credentials"},{"name":"Metering","description":"Usage metrics and billing information"},{"name":"Webhooks","description":"Webhook configuration and event delivery"},{"name":"Health","description":"Health checks and system status"}],"servers":[{"url":"https://sandbox.api.billerapi.com","description":"Sandbox"},{"url":"https://api.billerapi.com","description":"Production"}],"components":{"securitySchemes":{"client-api-key":{"scheme":"bearer","bearerFormat":"BillerAPI API key","type":"http","description":"BillerAPI API key. Use bak_test_... for sandbox and bak_live_... for production."},"jwt-auth":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"JWT token for authenticated users"}},"schemas":{"SignInDto":{"type":"object","properties":{"email":{"type":"string","description":"Worker email address","example":"worker@example.com"},"password":{"type":"string","description":"Worker password","example":"SecurePass123!"}},"required":["email","password"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","description":"Worker email address","example":"worker@example.com"},"password":{"type":"string","description":"Worker password","example":"SecurePass123!"},"given_name":{"type":"string","description":"Worker first name","example":"John"},"family_name":{"type":"string","description":"Worker last name","example":"Doe"},"phone_number":{"type":"string","description":"Worker phone number in E.164 format","example":"+15555555555"}},"required":["email","password","given_name","family_name","phone_number"]},"SignOutDto":{"type":"object","properties":{"refresh_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","description":"Refresh token to invalidate"}},"required":["refresh_token"]},"ConfirmSignUpDto":{"type":"object","properties":{"email":{"type":"string","description":"Worker email address","example":"worker@example.com"},"confirmation_code":{"type":"string","description":"Verification code from email","example":"123456"}},"required":["email","confirmation_code"]},"ConfirmSignUpAndSignInDto":{"type":"object","properties":{"email":{"type":"string","example":"developer@example.com","description":"User email address"},"confirmation_code":{"type":"string","example":"123456","description":"Confirmation code sent to email"},"password":{"type":"string","example":"SecurePassword123!","description":"User password (min 12 characters)"}},"required":["email","confirmation_code","password"]},"ForgotPasswordDto":{"type":"object","properties":{"email":{"type":"string","description":"Worker email address","example":"worker@example.com"}},"required":["email"]},"ResetPasswordDto":{"type":"object","properties":{"email":{"type":"string","description":"Worker email address","example":"worker@example.com"},"confirmation_code":{"type":"string","description":"Reset code sent to email","example":"123456"},"new_password":{"type":"string","description":"New password (min 12 characters)","example":"NewSecurePass123!"}},"required":["email","confirmation_code","new_password"]},"RefreshTokenDto":{"type":"object","properties":{"refresh_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","description":"Refresh token obtained from signin (optional — falls back to bb_refresh_token httpOnly cookie)"},"username":{"type":"string","description":"Cognito username (typically the email). Server derives it from id_token cookie when omitted. #2384"}}},"CreateClientDto":{"type":"object","properties":{"name":{"type":"string","example":"My Application","description":"Client application name"},"description":{"type":"string","example":"Production API client for MyApp","description":"Client description"},"redirect_uris":{"example":["https://myapp.com/callback"],"description":"Allowed redirect URIs for OAuth","type":"array","items":{"type":"string"}}},"required":["name"]},"LinkCustomizationDto":{"type":"object","properties":{"primary_color":{"type":"string","example":"#4F46E5","description":"Accent color as a strict 6-digit hex (#RRGGBB). Injected as a CSS custom property."},"mode":{"type":"string","example":"system","description":"Connect modal color mode.","enum":["light","dark","system"]},"border_radius":{"type":"string","example":"md","description":"Connect modal corner-radius preset.","enum":["sm","md","lg"]},"client_logo_url":{"type":"string","example":"https://cdn.customer.com/logo.png","description":"Self-hosted https logo URL rendered as <img src> in the consent handshake mark. https only — http, javascript:, data:, blob:, and non-URLs are rejected."},"pinned_biller_ids":{"example":["biller_eversource","biller_optimum"],"description":"Biller ids surfaced first in the modal popular list. Optimistic (no live existence check; unknown ids are skipped at render time). Max 24 string entries.","type":"array","items":{"type":"string"}}}},"UpdateClientDto":{"type":"object","properties":{"name":{"type":"string","example":"My Updated Application","description":"Client application name"},"description":{"type":"string","example":"Updated description","description":"Client description"},"environment":{"type":"string","example":"sandbox","description":"Client environment","enum":["sandbox","development","production"]},"status":{"type":"string","example":"active","description":"Client status"},"allowed_embed_origins":{"example":["https://app.customer.com"],"description":"Exact origins allowed to embed the hosted connect/elements iframes for this client (#4770). https only (http allowed for localhost), no paths or wildcards. Omit for no change; an empty array clears the registry (first-party-only embedding). Deep validation (scheme/path/wildcard rules) is enforced by the client-iam domain.","type":"array","items":{"type":"string"}},"redirect_uris":{"example":["https://app.customer.com/callback"],"description":"Registered OAuth redirect_uris (#4768). The hosted connect page top-navigates to redirect_uri on completion, so a hosted link token's redirect_uri must exactly match one of these at mint. Full callback URLs (paths + query allowed); https only (http allowed for localhost); no wildcards or fragments. Omit for no change; an empty array clears the allowlist.","type":"array","items":{"type":"string"}},"link_customization":{"description":"Dashboard-driven Connect (Link) customization (#4768 Phase 6.2): theme (color/mode/radius/logo) + pinned billers. Injected into the hosted Connect modal at link-token bootstrap. Omit for no change; an empty object clears customization back to default. Each field is re-validated in the client-iam domain (defense-in-depth).","allOf":[{"$ref":"#/components/schemas/LinkCustomizationDto"}]}}},"GenerateClientSecretDto":{"type":"object","properties":{"environment":{"type":"string","example":"sandbox","description":"Environment for the client secret","enum":["sandbox","development","production"]}},"required":["environment"]},"RegisterWebhookDto":{"type":"object","properties":{"environment":{"type":"string","example":"sandbox","description":"Environment for the webhook","enum":["sandbox","development","production"]},"url":{"type":"string","example":"https://myapp.com/webhooks/billbutler","description":"Webhook callback URL"},"events":{"example":["bill.created","bill.updated","link.connected"],"description":"Event types to subscribe to","type":"array","items":{"type":"string"}},"description":{"type":"string","example":"BillEBox partner-app webhook","description":"Human-readable description of the webhook"},"secret":{"type":"string","example":"whsec_secret123","description":"Webhook secret for signature verification"}},"required":["environment","url","events"]},"UpdateWebhookDto":{"type":"object","properties":{"webhook_url":{"type":"string","example":"https://myapp.com/webhooks/billerapi/v2","description":"Updated webhook callback URL"},"webhook_events":{"example":["bill.created","bill.updated","bill.deleted"],"description":"Updated event types","type":"array","items":{"type":"string"}},"description":{"type":"string","example":"BillEBox partner-app webhook","description":"Updated description"}},"required":["webhook_url","webhook_events"]},"SubmitVerificationRequestDto":{"type":"object","properties":{"company_legal_name":{"type":"string","example":"Acme Inc","description":"Legal business name"},"company_type":{"type":"string","example":"LLC","description":"Company type (e.g. LLC, Corporation)"},"website":{"type":"string","example":"https://acme.com","description":"Company website"},"use_case":{"type":"string","example":"Bill pay integration","description":"Primary use case"},"expected_volume":{"type":"string","example":"1000","description":"Expected monthly volume"}}},"TestWebhookDto":{"type":"object","properties":{"event_type":{"type":"string","example":"link.completed","description":"Subscribed event type to deliver as a synthetic test payload"}},"required":["event_type"]},"InternalUpdateClientDto":{"type":"object","properties":{"elements_embed_approved":{"type":"boolean","example":true,"description":"PCI-embed approval (#4895). When true, the hosted /elements card pages may be framed by this client's registered allowed_embed_origins (dynamic frame-ancestors). Default false (fail-closed; /elements stays first-party-only). Operator-only; requires CSO sign-off."}}},"UpdateProfileDto":{"type":"object","properties":{"worker_id":{"type":"string","description":"Worker ID","example":"worker_abc123"},"given_name":{"type":"string","description":"Updated first name","example":"Jane"},"family_name":{"type":"string","description":"Updated last name","example":"Smith"},"phone_number":{"type":"string","description":"Updated phone number in E.164 format","example":"+15555555556"}},"required":["worker_id"]},"GetBillsQueryDto":{"type":"object","properties":{"account_link_id":{"type":"string","example":"link-456","description":"Account link ID"},"start_date":{"type":"string","example":"2025-01-01","description":"Start date (ISO format)"},"end_date":{"type":"string","example":"2025-12-31","description":"End date (ISO format)"},"limit":{"type":"number","example":100,"description":"Maximum number of bills to return (1-500)"},"cursor":{"type":"string","example":"cursor_abc123","description":"Pagination cursor"}},"required":["account_link_id"]},"SyncBillsQueryDto":{"type":"object","properties":{"account_link_id":{"type":"string","example":"link-456","description":"Account link ID to sync"},"since":{"type":"string","example":"2025-01-01T00:00:00Z","description":"Sync bills since this date"},"limit":{"type":"number","example":100,"description":"Maximum number of bills to sync"},"include_deleted":{"type":"boolean","example":false,"description":"Include deleted bills"}},"required":["account_link_id"]},"TriggerBillSyncDto":{"type":"object","properties":{"account_link_id":{"type":"string","example":"link-456","description":"Account link ID to trigger sync for"},"force":{"type":"boolean","example":false,"description":"Force full sync even if recent sync exists"}},"required":["account_link_id"]},"StatementPeriodDto":{"type":"object","properties":{"start":{"type":"string","example":"2026-03-01","description":"First day of the statement window (YYYY-MM-DD)."},"end":{"type":"string","example":"2026-03-31","description":"Last day of the statement window (YYYY-MM-DD)."},"statement_date":{"type":"string","example":"2026-04-01","description":"When the statement was issued (YYYY-MM-DD). Always >= end."}},"required":["start","end","statement_date"]},"BalanceBreakdownDto":{"type":"object","properties":{"previous_balance":{"type":"number","example":1240.5},"payments_credits":{"type":"number","example":-1240.5,"description":"Negative for credits."},"new_charges":{"type":"number","example":873.42},"fees":{"type":"number","example":0},"interest":{"type":"number","example":0},"taxes":{"type":"number","example":0},"current_balance":{"type":"number","example":873.42,"description":"New balance / total currently due."},"minimum_payment_due":{"type":"number","example":35},"due_date":{"type":"string","example":"2026-04-28","description":"Payment due date (YYYY-MM-DD)."},"currency":{"type":"string","example":"USD","description":"#4040 PR4 — ISO-4217 currency for the whole statement. Every line item inherits this currency."},"reconciled":{"type":"boolean","example":true,"description":"#4040 PR3 — COMPUTED reconciliation flag (never the model self-report). true when the balance identity (previous_balance − |payments_credits| + new_charges + fees + interest + taxes ≈ current_balance) held within $0.01; false when it broke (the statement is downgraded to data_quality=PARTIAL + needs_review). Absent when there were not enough signed fields to compute it."}},"required":["current_balance","minimum_payment_due","due_date","currency"]},"BillerMetadataDto":{"type":"object","properties":{"masked_account_number":{"type":"string","example":"****4421"},"autopay_enabled":{"type":"boolean","example":true},"paperless":{"type":"boolean","example":true},"plan_name":{"type":"string","example":"Cash Rewards Visa"}},"required":["masked_account_number"]},"ServicePeriodDto":{"type":"object","properties":{"start":{"type":"string","example":"2026-04-01"},"end":{"type":"string","example":"2026-04-30"}},"required":["start","end"]},"LineItemUsageDto":{"type":"object","properties":{"quantity":{"type":"number","example":642,"description":"Quantity consumed (e.g. kWh, GB, minutes)."},"unit":{"type":"string","example":"kWh"},"rate":{"type":"number","example":0.1528,"description":"Per-unit rate."},"tier":{"type":"string","example":"tier-2"},"service_period":{"$ref":"#/components/schemas/ServicePeriodDto"}},"required":["quantity","unit"]},"LineItemDto":{"type":"object","properties":{"description":{"type":"string","example":"AMAZON MARKETPLACE","description":"Cleaned (trimmed, whitespace-collapsed)."},"description_raw":{"type":"string","example":"AMAZON  MARKETPLACE  ","description":"#4040 PR4 — verbatim source text."},"posted_date":{"type":"string","example":"2026-03-08"},"amount":{"type":"number","example":24.99,"description":"#4040 PR4 — ABSOLUTE value, always >= 0. The signed value is derived from `kind` (CREDIT/PAYMENT/ADJUSTMENT read negative)."},"kind":{"type":"string","enum":["CHARGE","USAGE_CHARGE","FEE","REGULATORY_FEE","TAX","INTEREST","ADJUSTMENT","CREDIT","PAYMENT","LATE_FEE","INSTALLMENT","DEPOSIT"],"example":"CHARGE","description":"#4040 PR4 — closed line-item kind vocabulary; the sole source of sign. CREDIT/PAYMENT/ADJUSTMENT make the signed amount negative."},"currency":{"type":"string","example":"USD","description":"#4040 PR4 — ISO-4217, inherited from the statement."},"category":{"type":"string","enum":["USAGE","SERVICE","EQUIPMENT","PLAN","PAYMENT","CREDIT","REFUND","PRINCIPAL","INTEREST","ESCROW","FEE","TAX","REGULATORY","LATE_FEE","GROCERIES","DINING","FUEL","TRAVEL","SHOPPING","SUBSCRIPTION","INSURANCE","OTHER"],"example":"SHOPPING","description":"#4040 PR4 — closed line-item category vocabulary (nullable)."},"reference_id":{"type":"string","example":"TRX-99183"},"quantity":{"type":"number","example":1},"usage":{"description":"#4040 PR4 — metered usage (utilities/telecom).","allOf":[{"$ref":"#/components/schemas/LineItemUsageDto"}]},"confidence":{"type":"number","example":0.95,"description":"Per-line confidence in [0, 1]."}},"required":["description","description_raw","posted_date","amount","kind","currency","confidence"]},"StatementCompletenessDto":{"type":"object","properties":{"fields_present":{"example":["statement_period","balance_breakdown","line_items"],"description":"Top-level field paths populated in this statement.","type":"array","items":{"type":"string"}},"fields_missing":{"example":[],"description":"Top-level field paths the agent could not populate.","type":"array","items":{"type":"string"}},"reasons":{"type":"object","example":{},"description":"Per-missing-field reason codes (e.g. \"biller_does_not_disclose\").","additionalProperties":{"type":"string"}},"refreshable":{"type":"boolean","example":false,"description":"Whether a future POST /refresh might fill the gaps. False if the biller permanently does not disclose this data."},"data_quality":{"type":"string","enum":["VERIFIED","INFERRED","PARTIAL"],"example":"VERIFIED","description":"VERIFIED = all required fields exact-match. INFERRED = some derived from indirect signals. PARTIAL = at least one required field missing."},"extraction_method":{"type":"string","enum":["CREDENTIALED","EMAIL","BANK_FEED","USER_UPLOAD"],"example":"CREDENTIALED","description":"How BillerAPI obtained the underlying document. User-comprehensible category — does NOT leak internal pipeline mechanics (scrape vs IMAP vs OFX)."},"needs_review":{"type":"boolean","example":false,"description":"true when this statement was persisted below the extraction confidence threshold (or otherwise flagged) and should be re-checked. Always paired with data_quality=PARTIAL. Recipients may call POST /refresh to attempt a higher-confidence re-extraction."},"needs_review_reason":{"type":"string","example":"overall_confidence below threshold","description":"Short machine-readable reason for needs_review, when set."},"truncated":{"type":"boolean","example":false,"description":"#4040 PR4 — true when the source emitted more than 500 line items and the tail was dropped (the first 500 are kept). Computed at ingest, never the model self-report."}},"required":["fields_present","fields_missing","reasons","refreshable","data_quality","extraction_method"]},"StatementDto":{"type":"object","properties":{"bill_id":{"type":"string","example":"bill_01HXY1234567890ABCDEFGHJK"},"client_id":{"type":"string","example":"client_01HXY..."},"version":{"type":"number","example":3,"description":"Optimistic concurrency version. Increments on every refresh."},"extracted_at":{"type":"string","example":"2026-04-25T14:32:00.000Z","description":"When BillerAPI extracted this snapshot from the source document. Use this, not fetched_at — fetched_at was renamed pre-launch."},"stale":{"type":"boolean","example":false,"description":"true when the statement is older than its TTL or upstream marked it stale; recipients should call POST /refresh."},"statement_period":{"$ref":"#/components/schemas/StatementPeriodDto"},"balance_breakdown":{"$ref":"#/components/schemas/BalanceBreakdownDto"},"biller_metadata":{"$ref":"#/components/schemas/BillerMetadataDto"},"line_items":{"description":"Capped at 500 per statement for v1.","type":"array","items":{"$ref":"#/components/schemas/LineItemDto"}},"completeness":{"$ref":"#/components/schemas/StatementCompletenessDto"},"overall_confidence":{"type":"number","example":0.97,"description":"Aggregate confidence in [0, 1]."}},"required":["bill_id","client_id","version","extracted_at","stale","statement_period","balance_breakdown","biller_metadata","line_items","completeness","overall_confidence"]},"RefreshStatementBodyDto":{"type":"object","properties":{"source_hint":{"type":"string","enum":["CREDENTIALED","EMAIL","BANK_FEED","USER_UPLOAD"],"description":"Optional hint about which pipeline to prefer for the refresh. Best-effort."}}},"RefreshStatementResponseDto":{"type":"object","properties":{"request_id":{"type":"string","example":"d94f5e2a-8c3b-4f1e-9a7d-6b2c1e0f8a34"},"status":{"type":"string","example":"pending","enum":["pending","complete","failed"]},"started_at":{"type":"string","example":"2026-04-25T14:32:00.000Z"}},"required":["request_id","status","started_at"]},"ApiErrorDto":{"type":"object","properties":{"error_code":{"type":"string","enum":["BILL_NOT_FOUND","INVALID_CURSOR","ACCOUNT_LINK_NOT_FOUND","STATEMENT_NOT_EXTRACTED","EXTRACTION_FAILED","EXTRACTION_UNSUPPORTED_FOR_BILLER","STATEMENT_TOO_LARGE","RATE_LIMITED","UNAUTHORIZED","IDEMPOTENCY_KEY_MISMATCH","PAYMENT_EXECUTION_NOT_AVAILABLE","FEEDBACK_RUN_NOT_FOUND","FEEDBACK_RUN_NOT_OWNED","FEEDBACK_RUN_EXPIRED","FEEDBACK_INVALID_CATEGORY","FEEDBACK_INVALID_SIGNAL","FEEDBACK_ALREADY_SUBMITTED","FEEDBACK_RATE_LIMITED","MESSAGING_CONSENT_NOT_GRANTED","MESSAGING_LIVE_ACCESS_REQUIRED","MESSAGING_CONTENT_FLAGGED","MESSAGING_RATE_LIMITED","MESSAGING_SUPPRESSED","MESSAGING_AUP_NOT_ACCEPTED","MESSAGING_AUP_REACCEPT_REQUIRED","MESSAGING_INVALID_PAYLOAD","MESSAGING_INVALID_CATEGORY","MESSAGING_PERSIST_FAILED","MESSAGING_INTERNAL_ERROR","MESSAGING_NOT_FOUND","LINK_TOKEN_NOT_FOUND","BACKGROUND_NOT_ELIGIBLE","BILLER_UNSUPPORTED","UPDATE_BILLER_MISMATCH","INVALID_UPDATE_REASON","LINK_UPDATE_FORBIDDEN","LINK_NOT_FOUND","BILLER_NOT_FOUND","REDIRECT_URI_NOT_REGISTERED"],"example":"STATEMENT_NOT_EXTRACTED","description":"Stable identifier — third parties branch on this. Additive-only contract."},"error_type":{"type":"string","enum":["invalid_request","rate_limit","auth","upstream","api_error"],"example":"invalid_request","description":"Coarse category clients branch on for retry/backoff."},"error_message":{"type":"string","example":"Statement has not yet been extracted for bill bill_abc123. Call POST /v1/bills/:bill_id/statement/refresh to trigger extraction.","description":"Human-readable explanation. Safe to show end users in dev contexts."},"hint":{"type":"string","example":"Call POST /v1/bills/:bill_id/statement/refresh, then retry after the statement is extracted.","description":"Actionable next step for resolving this error."},"request_id":{"type":"string","example":"d94f5e2a-8c3b-4f1e-9a7d-6b2c1e0f8a34","description":"Correlation id (UUID) for support tickets. Also returned as the X-Request-Id response header on every response."},"docs_url":{"type":"string","example":"https://docs.billerapi.com/errors/STATEMENT_NOT_EXTRACTED","description":"Canonical, LIVE docs page for this error code."},"documentation_url":{"type":"string","example":"https://docs.billerapi.com/errors/STATEMENT_NOT_EXTRACTED","deprecated":true,"description":"DEPRECATED alias for docs_url, retained for back-compat during the DX P0 migration window. Read docs_url instead — this field will be removed."},"retry_after":{"type":"number","example":30,"description":"On 429, the seconds the client should wait before retrying."},"errors":{"type":"array","description":"On 400 validation failures, the per-field problems.","example":[{"param":"given_name","code":"required","message":"given_name should not be empty"}]}},"required":["error_code","error_type","error_message","hint","request_id","docs_url"]},"ListInsightsQueryDto":{"type":"object","properties":{"account_link_id":{"type":"string","example":"alink_01HX...","description":"The account_link_id (biller connection) to list insights for."},"account_id":{"type":"string","example":"acct_01HX...","description":"The account_id (the account within the connection) to list insights for."},"user_aid":{"type":"string","example":"user_42","description":"Optional filter to insights for a specific end-user (your client_user_id)."},"status":{"type":"string","example":"OPEN","description":"Filter by lifecycle status.","enum":["OPEN","SNOOZED","DISMISSED","RESOLVED"]},"limit":{"type":"number","example":50,"description":"Maximum number of insights to return (1-100)."},"cursor":{"type":"string","example":"cursor_abc123","description":"Opaque pagination cursor from a prior response."}},"required":["account_link_id","account_id"]},"SnoozeInsightBodyDto":{"type":"object","properties":{"until":{"type":"string","example":"2026-07-01T00:00:00.000Z","description":"ISO-8601 instant to snooze the insight until. Must be in the future."}},"required":["until"]},"ResolveInsightBodyDto":{"type":"object","properties":{"resolved_at":{"type":"string","example":"2026-06-13T12:00:00.000Z","description":"Optional ISO-8601 instant the insight was resolved at. Defaults to server-now."}}},"ProcessBillDto":{"type":"object","properties":{"worker_id":{"type":"string","example":"worker-123","description":"Worker ID processing the bill"},"notes":{"type":"string","example":"Verified bill amount and due date","description":"Processing notes"}},"required":["worker_id"]},"ApproveBillDto":{"type":"object","properties":{"worker_id":{"type":"string","example":"worker-123","description":"Worker ID approving the bill"},"notes":{"type":"string","example":"Bill verified and approved for payment","description":"Approval notes"}},"required":["worker_id"]},"RejectBillDto":{"type":"object","properties":{"worker_id":{"type":"string","example":"worker-123","description":"Worker ID rejecting the bill"},"reason":{"type":"string","example":"Amount mismatch - requires user verification","description":"Rejection reason"}},"required":["worker_id","reason"]},"SyncBillsDto":{"type":"object","properties":{"client_id":{"type":"string","example":"test-client-123","description":"Client ID to sync bills for"},"account_link_ids":{"example":["link-456","link-789"],"description":"Account link IDs to sync (optional)","type":"array","items":{"type":"string"}}},"required":["client_id"]},"ListBillsQueryDto":{"type":"object","properties":{"status":{"type":"string"},"biller_id":{"type":"string"},"account_link_id":{"type":"string"},"start_date":{"type":"string"},"end_date":{"type":"string"},"limit":{"type":"number","default":100},"cursor":{"type":"string"}}},"MoneyValueDto":{"type":"object","properties":{"value":{"type":"number","example":100.5,"description":"Amount value"},"currency":{"type":"string","example":"USD","description":"Currency code","default":"USD"}},"required":["value","currency"]},"CreateBillDto":{"type":"object","properties":{"account_link_id":{"type":"string","example":"link-456","description":"Account link ID"},"biller_id":{"type":"string","example":"biller-123","description":"Biller ID"},"bill_number":{"type":"string","example":"BILL-2025-001","description":"Bill number"},"amount":{"description":"Bill amount","allOf":[{"$ref":"#/components/schemas/MoneyValueDto"}]},"due_date":{"type":"string","example":"2025-02-15T00:00:00Z","description":"Due date (ISO format)"},"description":{"type":"string","example":"Monthly utility bill","description":"Bill description"},"client_id":{"type":"string","example":"client-123","description":"Client ID"},"metadata":{"type":"object","example":{"category":"UTILITIES","userId":"user-123"},"description":"Additional metadata"},"pdf_file":{"type":"object","example":{"file_name":"bill.pdf","file_size":1024,"file_url":"https://example.com/bill.pdf","mime_type":"application/pdf","checksum":"abc123"},"description":"PDF file metadata"}},"required":["account_link_id","biller_id","bill_number","amount","due_date"]},"LoginToBillerDto":{"type":"object","properties":{"biller_id":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"},"user_id":{"type":"string"},"mfa_code":{"type":"string"}},"required":["biller_id","username","password","user_id"]},"DiscoverBillerAccountsDto":{"type":"object","properties":{"biller_id":{"type":"string"},"user_id":{"type":"string"},"link_token":{"type":"string"}},"required":["biller_id","user_id","link_token"]},"RequestBillerOnboardingDto":{"type":"object","properties":{"detection_id":{"type":"string"},"biller_name":{"type":"string"},"biller_type":{"type":"string"},"requested_by":{"type":"string"},"client_id":{"type":"string"},"detected_data_json":{"type":"string"},"review_task_id":{"type":"string"},"message_id":{"type":"string"}},"required":["biller_name","biller_type","requested_by","detected_data_json"]},"UserInitiatedBillerOnboardingRequestDto":{"type":"object","properties":{"client_user_id":{"type":"string","description":"BillEBox end-user identifier (bare cognito sub)."},"biller_name":{"type":"string","description":"Human-typed biller name."},"biller_type":{"type":"string","description":"DEPRECATED (#2754) — ignored. Agent classifies during onboarding.","enum":["UTILITY","TELECOM","INSURANCE","CREDIT_CARD","LOAN","SUBSCRIPTION","BANKING","OTHER"],"deprecated":true},"biller_url":{"type":"string","description":"Biller public site or pay URL. Normalized + SSRF-checked server-side."}},"required":["client_user_id","biller_name","biller_url"]},"UpdateBillerTypeDto":{"type":"object","properties":{"biller_type":{"type":"string","description":"Corrected biller category.","enum":["UTILITY","TELECOM","INSURANCE","CREDIT_CARD","LOAN","SUBSCRIPTION","BANKING","OTHER"]}},"required":["biller_type"]},"CreateBillerConfigDto":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"base_url":{"type":"string"},"credentials_required":{"type":"boolean"},"mfa_required":{"type":"boolean"},"session_timeout_minutes":{"type":"number"},"max_retry_attempts":{"type":"number"},"connection_methods_json":{"type":"string","description":"JSON-encoded BillerConnectionMethod[] (scraping | oauth | api_key arms)."},"default_connection_kind":{"type":"string","description":"scraping | oauth | api_key — must match a kind in connection_methods_json."},"identity_registry_json":{"type":"string","description":"JSON-encoded BillerIdentityRegistry (sender emails + websites)."},"supported_consent_scopes":{"type":"array","items":{"type":"string"}},"metadata_json":{"type":"string"}},"required":["name","type","status","base_url","credentials_required","mfa_required","session_timeout_minutes","max_retry_attempts"]},"UpdateBillerConfigDto":{"type":"object","properties":{"company_name":{"type":"string","description":"Company name"},"website":{"type":"string","description":"Company website URL"},"support_email":{"type":"string","description":"Support email address"},"support_phone":{"type":"string","description":"Support phone number"},"integration_type":{"type":"string","description":"Integration type","enum":["OAUTH_API","IP_WHITELIST","DOCKER_GATEWAY","HYBRID","DIRECT_API"]},"integration_config":{"type":"object","description":"Type-specific integration configuration"}}},"SaveRecordedSelectorsDto":{"type":"object","properties":{"selector_type":{"type":"string","enum":["login","account_discovery","retrieve_bill","retrieve_observed_payment"],"description":"Which scraping selector bucket this recording targets."},"selectors_json":{"type":"string","description":"JSON-encoded recorded selectors — flat snake_case map or { webScraping: { selectors } }."},"login_url":{"type":"string","description":"Page URL where the selectors were captured. Recommended for `login` so replays know where to navigate."}},"required":["selector_type","selectors_json"]},"AdditionalRecordingOriginEntryDto":{"type":"object","properties":{"origin":{"type":"string","example":"i-doxs.net","description":"Bare eTLD+1 (registrable domain). Lowercase, no scheme, no path, no port, no subdomain."},"vendor":{"type":"string","example":"Kubra","description":"Operator-facing vendor label (display only)."},"reason":{"type":"string","example":"Eversource bills viewer hosted on Kubra portal","description":"Free-text operator justification (for audit display)."}},"required":["origin"]},"UpdateAdditionalRecordingOriginsDto":{"type":"object","properties":{"entries":{"description":"Full replacement list of allowlisted origins. Empty array clears the allowlist; the runtime gateway origin check then falls back to base_url match only.","type":"array","items":{"$ref":"#/components/schemas/AdditionalRecordingOriginEntryDto"}}},"required":["entries"]},"RecipePreferenceDto":{"type":"object","properties":{"recipe_replay_override":{"type":"string","enum":["force_on","force_off",""],"example":"force_on","description":"Tri-state replay override. 'force_on'/'force_off' override the global BILLERAPI_RECIPE_REPLAY_ENABLED flag; '' clears the override (follow-global)."}},"required":["recipe_replay_override"]},"BulkUpdateBillerConfigsDto":{"type":"object","properties":{"biller_ids":{"type":"array","items":{"type":"string"}},"updates_json":{"type":"string"}},"required":["biller_ids","updates_json"]},"BulkDeleteBillerConfigsDto":{"type":"object","properties":{"biller_ids":{"type":"array","items":{"type":"string"}}},"required":["biller_ids"]},"CreateFromTemplateDto":{"type":"object","properties":{"template_name":{"type":"string"},"biller_name":{"type":"string"},"customizations_json":{"type":"string"}},"required":["template_name","biller_name"]},"ExecuteAutomationDto":{"type":"object","properties":{"biller_id":{"type":"string"},"automation_type":{"type":"string"},"parameters_json":{"type":"string"}},"required":["biller_id","automation_type"]},"ClaimBillerOnboardingDto":{"type":"object","properties":{"worker_id":{"type":"string","description":"Worker taking or releasing the claim"}},"required":["worker_id"]},"SelectExistingBillerDto":{"type":"object","properties":{"biller_id":{"type":"string"},"selected_by":{"type":"string"}},"required":["biller_id","selected_by"]},"CompleteBillerOnboardingReviewDto":{"type":"object","properties":{"edited_by":{"type":"string"},"draft_config_json":{"type":"string"},"override":{"type":"object","description":"Override for the strong-match gate (set when operator chose create-new despite suggestions)","example":{"reason_code":"different_region_or_entity","reason_text":"East coast subsidiary, separate billing entity","suggested_biller_ids":["biller-abc"]}}},"required":["edited_by","draft_config_json"]},"VerifyBillerDto":{"type":"object","properties":{"verified_by":{"type":"string"}},"required":["verified_by"]},"RejectBillerVerificationDto":{"type":"object","properties":{"rejection_reason":{"type":"string"},"rejected_by":{"type":"string"}},"required":["rejection_reason","rejected_by"]},"ClaimEmailReviewDto":{"type":"object","properties":{"worker_id":{"type":"string","description":"Worker taking or releasing the claim"}},"required":["worker_id"]},"GetApiCallRecordsQueryDto":{"type":"object","properties":{"client_id":{"type":"string","example":"test-client-123","description":"Client ID"},"start_date":{"type":"string","example":"2025-01-01","description":"Start date (ISO format)"},"end_date":{"type":"string","example":"2025-12-31","description":"End date (ISO format)"},"limit":{"type":"string","example":"100","description":"Maximum number of records to return"},"include_portal_noise":{"type":"string","example":"false","description":"When 'true', include portal-browsing requests (session-mode 2xx GETs) in the response. Default 'false' hides them. Errors and integration calls are always shown regardless.","enum":["true","false"]},"env":{"type":"string","description":"DEPRECATED — value ignored. Removal scheduled 2026-08-23.","deprecated":true},"environment":{"type":"string","description":"DEPRECATED — value ignored. Removal scheduled 2026-08-23.","deprecated":true}},"required":["client_id"]},"GetWebhookDeliveryRecordsQueryDto":{"type":"object","properties":{"webhook_url":{"type":"string","example":"https://example.com/webhooks","description":"Webhook URL to filter delivery records"},"client_id":{"type":"string","example":"test-client-123","description":"Client ID"},"start_date":{"type":"string","example":"2025-01-01","description":"Start date (ISO format)"},"end_date":{"type":"string","example":"2025-12-31","description":"End date (ISO format)"},"event_type":{"type":"string","example":"bill.created","description":"Event type filter"},"delivery_status":{"type":"string","example":"delivered","description":"Delivery status filter"},"limit":{"type":"number","example":50,"description":"Max records to return"}}},"GetActiveAccountCountQueryDto":{"type":"object","properties":{"client_id":{"type":"string","example":"test-client-123","description":"Client ID"},"date":{"type":"string","example":"2025-11-01","description":"Date to check active accounts (ISO format)"}},"required":["client_id"]},"GetAccountSnapshotQueryDto":{"type":"object","properties":{"client_id":{"type":"string","example":"test-client-123","description":"Client ID"},"date":{"type":"string","example":"2025-11-01","description":"Snapshot date (ISO format)"}},"required":["client_id"]},"RecordApiCallDto":{"type":"object","properties":{"client_id":{"type":"string","example":"test-client-123","description":"Client ID making the API call"},"endpoint":{"type":"string","example":"/bills","description":"API endpoint called"},"method":{"type":"string","example":"GET","description":"HTTP method"},"status_code":{"type":"number","example":200,"description":"Response status code"},"response_time":{"type":"number","example":125,"description":"Response time in milliseconds"}},"required":["client_id","endpoint","method"]},"RecordWebhookDeliveryDto":{"type":"object","properties":{"client_id":{"type":"string","example":"test-client-123","description":"Client ID receiving the webhook"},"webhook_id":{"type":"string","example":"webhook-789","description":"Webhook ID"},"event_type":{"type":"string","example":"bill.created","description":"Event type"},"status_code":{"type":"number","example":200,"description":"Delivery status code"},"success":{"type":"boolean","example":true,"description":"Whether delivery was successful"}},"required":["client_id","webhook_id","event_type","success"]},"RecordAccountLinkDto":{"type":"object","properties":{"client_id":{"type":"string","example":"test-client-123","description":"Client ID"},"account_link_id":{"type":"string","example":"link-456","description":"Account link ID"},"status":{"type":"string","example":"ACTIVE","description":"Link status"}},"required":["client_id","account_link_id","status"]},"RecordWorkerTaskCompletionDto":{"type":"object","properties":{"worker_id":{"type":"string","example":"worker-123","description":"Worker ID"},"task_type":{"type":"string","example":"BILL_PROCESSING","description":"Task type"},"duration_seconds":{"type":"number","example":320,"description":"Task duration in seconds"},"success":{"type":"boolean","example":true,"description":"Whether task was successful"}},"required":["worker_id","task_type","success"]},"SnapshotAccountCountsDto":{"type":"object","properties":{"client_id":{"type":"string","example":"test-client-123","description":"Client ID to snapshot"},"date":{"type":"string","example":"2025-11-01","description":"Snapshot date (ISO format)"}},"required":["client_id"]},"GetWorkerStatsQueryDto":{"type":"object","properties":{"worker_id":{"type":"string","example":"worker-123","description":"Worker ID"},"start_date":{"type":"string","example":"2025-01-01","description":"Start date (ISO format)"},"end_date":{"type":"string","example":"2025-12-31","description":"End date (ISO format)"}}},"GetWorkerTaskHistoryQueryDto":{"type":"object","properties":{"worker_id":{"type":"string","example":"worker-123","description":"Worker ID"},"start_date":{"type":"string","example":"2025-01-01","description":"Start date (ISO format)"},"end_date":{"type":"string","example":"2025-12-31","description":"End date (ISO format)"}},"required":["worker_id"]},"AdjudicateSpotCheckDto":{"type":"object","properties":{}},"DashboardClientsDto":{"type":"object","properties":{"total":{"type":"number"}},"required":["total"]},"BillerPipelineItemDto":{"type":"object","properties":{"biller_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string"}},"required":["biller_id","name","status","created_at"]},"DashboardBillersDto":{"type":"object","properties":{"total":{"type":"number"},"active":{"type":"number"},"inactive":{"type":"number"},"maintenance":{"type":"number"},"onboarding_pipeline":{"type":"array","items":{"$ref":"#/components/schemas/BillerPipelineItemDto"}}},"required":["total","active","inactive","maintenance","onboarding_pipeline"]},"DashboardAccountsDto":{"type":"object","properties":{"total_linked":{"type":"number"},"active":{"type":"number"},"pending":{"type":"number"},"error":{"type":"number"}},"required":["total_linked","active","pending","error"]},"DashboardBillsDto":{"type":"object","properties":{"fetched_count":{"type":"number"},"paid_count":{"type":"number"},"fetch_success_rate":{"type":"number"}},"required":["fetched_count","paid_count","fetch_success_rate"]},"DashboardOperationsDto":{"type":"object","properties":{"api_call_count":{"type":"number"},"worker_success_rate":{"type":"number"},"avg_task_duration_ms":{"type":"number"}},"required":["api_call_count","worker_success_rate","avg_task_duration_ms"]},"DashboardOnboardingCostDto":{"type":"object","properties":{"avg_tasks_per_biller":{"type":"number"},"avg_duration_per_biller_ms":{"type":"number"}},"required":["avg_tasks_per_biller","avg_duration_per_biller_ms"]},"DashboardSummaryDto":{"type":"object","properties":{"clients":{"$ref":"#/components/schemas/DashboardClientsDto"},"billers":{"$ref":"#/components/schemas/DashboardBillersDto"},"accounts":{"$ref":"#/components/schemas/DashboardAccountsDto"},"bills":{"$ref":"#/components/schemas/DashboardBillsDto"},"operations":{"$ref":"#/components/schemas/DashboardOperationsDto"},"onboarding_cost":{"$ref":"#/components/schemas/DashboardOnboardingCostDto"}},"required":["clients","billers","accounts","bills","operations","onboarding_cost"]},"ReplayDeadLetterDto":{"type":"object","properties":{"client_id":{"type":"string","description":"Owning client of the dead letter (dead letters are client-partitioned)"},"replay_reason":{"type":"string","description":"Reason shown as \"last replayed by X: <reason>\""}},"required":["client_id"]},"CreateRequestToLinkDto":{"type":"object","properties":{"client_id":{"type":"string","description":"Client tenant id (your account_id under BillerAPI).","example":"cli_abc123"},"client_user_id":{"type":"string","description":"Your end-user identifier — opaque to BillerAPI; ties an RTL to one user in your system.","example":"usr_xyz789"},"biller_id":{"type":"string","description":"Biller id (call GET /billers for the catalog). Required: every RTL must resolve to a biller so Link + webhook fanout work.","example":"test_electric_company"},"onboarding_request_id":{"type":"string","description":"Onboarding-request id when the RTL was minted off a freshly-onboarded biller."},"metadata":{"type":"object","description":"Arbitrary client-supplied metadata, echoed back on GET.","additionalProperties":true}},"required":["client_id","client_user_id","biller_id"]},"UpdateRequestToLinkStatusDto":{"type":"object","properties":{}},"CancelRequestToLinkDto":{"type":"object","properties":{}},"CreateWebhookEndpointDto":{"type":"object","properties":{"url":{"type":"string","example":"https://myapp.com/webhooks/billerapi","description":"Destination URL BillerAPI POSTs signed events to."},"events":{"example":["bill.created","bill.updated","link.connected"],"description":"Event types this endpoint subscribes to (per-endpoint filter).","type":"array","items":{"type":"string"}},"description":{"type":"string","example":"Prod billing sync endpoint","description":"Human-readable label for the endpoint."}},"required":["url","events"]},"UpdateWebhookEndpointDto":{"type":"object","properties":{"url":{"type":"string","example":"https://myapp.com/webhooks/billerapi/v2","description":"Updated destination URL."},"events":{"example":["bill.created","bill.deleted"],"description":"Replacement event filter for this endpoint.","type":"array","items":{"type":"string"}},"enabled":{"type":"boolean","example":false,"description":"Enable or disable deliveries to this endpoint."},"description":{"type":"string","example":"Renamed endpoint","description":"Updated human-readable label."}}},"SendCustomerMessageDto":{"type":"object","properties":{}},"UpdateCustomerConsentDto":{"type":"object","properties":{}},"RecordCustomerComplaintDto":{"type":"object","properties":{}},"ParameterResponseDto":{"type":"object","properties":{"name":{"type":"string","description":"Parameter name","example":"/billerapi/iam-service/dev/PORT"},"value":{"type":"string","description":"Parameter value (masked for SecureString)","example":"3001"},"type":{"type":"string","description":"Parameter type","enum":["String","StringList","SecureString"],"example":"String"},"serviceName":{"type":"string","description":"Service name","example":"iam-service"},"lastModifiedDate":{"type":"string","description":"Last modified date","example":"2025-01-15T10:30:00Z"},"version":{"type":"number","description":"Parameter version","example":1},"description":{"type":"string","description":"Parameter description or ARN"}},"required":["name","value","type","serviceName","lastModifiedDate","version"]},"ServiceInfoDto":{"type":"object","properties":{"serviceName":{"type":"string","description":"Service name","example":"iam-service"},"count":{"type":"number","description":"Number of parameters for this service","example":10}},"required":["serviceName","count"]},"UpdateParameterDto":{"type":"object","properties":{"name":{"type":"string","description":"Full parameter name (must start with /billerapi/)","example":"/billerapi/iam-service/dev/PORT"},"value":{"type":"string","description":"New parameter value","example":"3001"},"type":{"type":"string","description":"Parameter type","enum":["String","StringList","SecureString"],"example":"String"}},"required":["name","value","type"]},"EventResponseDto":{"type":"object","properties":{"eventId":{"type":"string","description":"Unique event identifier"},"eventType":{"type":"string","description":"Event type name"},"source":{"type":"string","description":"Source service that published the event"},"timestamp":{"type":"string","description":"Event timestamp"},"aggregateId":{"type":"string","description":"Aggregate ID associated with the event"},"correlationId":{"type":"string","description":"Correlation ID for tracking related events"},"userId":{"type":"string","description":"User ID associated with the event"},"payload":{"type":"object","description":"Event payload data"},"metadata":{"type":"object","description":"Event metadata"}},"required":["eventId","eventType","source","timestamp","payload","metadata"]},"EventQueryResponseDto":{"type":"object","properties":{"events":{"description":"Array of events matching the query","type":"array","items":{"$ref":"#/components/schemas/EventResponseDto"}},"lastKey":{"type":"string","description":"Pagination key for next page"},"total":{"type":"number","description":"Total number of events returned in this page"}},"required":["events","total"]},"BillerSignUpDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address for the biller account"},"password":{"type":"string","description":"Password (minimum 12 characters)"},"given_name":{"type":"string","description":"First name"},"family_name":{"type":"string","description":"Last name"},"company_name":{"type":"string","description":"Company name"}},"required":["email","password","given_name","family_name"]},"BillerConfirmSignUpDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address used during signup"},"confirmation_code":{"type":"string","description":"Verification code sent to email"}},"required":["email","confirmation_code"]},"BillerSignInDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address"},"password":{"type":"string","description":"Account password"}},"required":["email","password"]},"BillerForgotPasswordDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address for password reset"}},"required":["email"]},"BillerConfirmForgotPasswordDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address"},"confirmation_code":{"type":"string","description":"Verification code sent to email"},"new_password":{"type":"string","description":"New password (minimum 12 characters)"}},"required":["email","confirmation_code","new_password"]},"BillerResendCodeDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address to resend verification code"}},"required":["email"]},"SubmitBillerVerificationDto":{"type":"object","properties":{"biller_id":{"type":"string"},"legal_business_name":{"type":"string"},"ein_tax_id":{"type":"string"},"business_address":{"type":"string"},"website_url":{"type":"string"},"biller_type":{"type":"string"}},"required":["biller_id","legal_business_name","business_address","website_url","biller_type"]},"SubmitOnboardingDto":{"type":"object","properties":{"company_name":{"type":"string","description":"Company name"},"website":{"type":"string","description":"Company website URL"},"support_email":{"type":"string","description":"Support email address"},"support_phone":{"type":"string","description":"Support phone number"},"integration_type":{"type":"string","description":"Integration type","enum":["OAUTH_API","IP_WHITELIST","DOCKER_GATEWAY","HYBRID","DIRECT_API"]},"integration_config":{"type":"object","description":"Type-specific integration configuration"}},"required":["company_name","support_email","integration_type"]},"ReportScriptIssueDto":{"type":"object","properties":{"description":{"type":"string","description":"Description of the issue"}},"required":["description"]},"InviteTeamMemberDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address of the team member to invite"},"role":{"type":"string","description":"Role to assign","enum":["admin","editor","viewer"]}},"required":["email","role"]},"RecordApiRecipeDto":{"type":"object","properties":{}},"RecordApiRawTraceIndexDto":{"type":"object","properties":{}},"ReplayActivityDto":{"type":"object","properties":{"delivery_id":{"type":"string","description":"Delivery id (matches the activity row resource_id for webhook events)"},"replay_reason":{"type":"string","description":"Why the developer is resending (3-280 chars; surfaced on the row)."}},"required":["delivery_id","replay_reason"]},"ReplayWebhookDto":{"type":"object","properties":{"client_id":{"type":"string","description":"Client ID (for ownership validation)","example":"client-123"}},"required":["client_id"]},"ReplayDeliveryDto":{"type":"object","properties":{"replay_reason":{"type":"string","description":"Why the developer is resending (3-280 chars; surfaced on the row as \"last replayed by X: <reason>\")"}},"required":["replay_reason"]},"ResendFailedWebhooksDto":{"type":"object","properties":{"since":{"type":"string","description":"ISO-8601 lower bound — only failed deliveries with exhausted_at >= since are resent","example":"2026-07-06T00:00:00.000Z"},"webhook_url":{"type":"string","description":"Optional endpoint filter — only resend failed deliveries for this webhook URL"},"replay_reason":{"type":"string","description":"Why the developer is resending (3-280 chars; recorded on every replayed delivery)"}},"required":["since","replay_reason"]},"StorePaymentMethodDto":{"type":"object","properties":{"pan":{"type":"string","description":"PAN (digits, 13–19 chars)"},"cvv":{"type":"string","description":"CVV (3–4 digits)"},"expiry_month":{"type":"number","description":"Expiry month (1–12)","minimum":1,"maximum":12},"expiry_year":{"type":"number","description":"Expiry year (4-digit)"},"bucket":{"type":"string","description":"Bucket label (e.g. \"primary\")"}},"required":["pan","expiry_month","expiry_year"]},"InitiatePaymentDto":{"type":"object","properties":{"bill_id":{"type":"string"},"payment_method_id":{"type":"string"},"idempotency_key":{"type":"string","description":"Idempotency key (optional in body, can also come from Idempotency-Key header)"},"amount_minor_units":{"type":"number","description":"Amount in minor units (cents). Defaults to bill amount."},"currency":{"type":"string","description":"3-letter currency code"},"execute_at":{"type":"string","description":"ISO-8601 timestamp to execute the payment at (#5072 scheduled pay). Must be in the future, at most 366 days out. Omit for immediate execution."}},"required":["bill_id","payment_method_id"]},"ResolvePaymentAttemptDto":{"type":"object","properties":{"resolved_to":{"type":"string","enum":["PAID","FAILED"],"description":"Final state the operator chose"},"reason":{"type":"string","description":"Audit reason for the resolution"},"confirmation_number":{"type":"string","description":"Confirmation number (required when resolved_to=PAID)"},"error_code":{"type":"string","description":"Payment error code (optional when resolved_to=FAILED)"}},"required":["resolved_to","reason"]},"SubmitFeedbackBodyDto":{"type":"object","properties":{"signal":{"type":"string","enum":["succeeded","login_failed","bill_missing","data_incorrect"],"example":"data_incorrect","description":"The signal the client (or the client's end-user) reports about this scraping run. Subset of the implicit signal vocabulary — clients can only report values they can meaningfully observe (success, login fail, missing bill, wrong data). The system-only values (timeout, two_factor_required, extraction_empty) are not in this enum."},"biller_id":{"type":"string","example":"biller_verizon","description":"Biller ID the scraping run targeted. Used for per-biller feedback aggregation."},"by_user_id":{"type":"string","example":"user_42","description":"The end-user identifier within the client's domain who saw the result. Useful for audit and for per-user dedup downstream. Optional — clients without a user concept can omit it."},"notes":{"type":"string","example":"Bill shows $0 but my paper bill says $84.32. Acct ending 1234.","description":"Free-text notes. Never echoed back in metrics; PII-safe storage required by client."}},"required":["signal","biller_id"]},"FeedbackResponseDto":{"type":"object","properties":{"agent_type":{"type":"string","example":"recording_agent"},"run_id":{"type":"string","example":"run_abc123"},"client_id":{"type":"string","example":"client_billebox"},"biller_id":{"type":"string","example":"biller_verizon"},"signal":{"type":"string","enum":["succeeded","login_failed","bill_missing","data_incorrect"],"example":"data_incorrect"},"by_user_id":{"type":"string","example":"user_42"},"notes":{"type":"string","example":"Bill shows $0 ..."},"submitted_at":{"type":"string","example":"2026-05-30T18:00:00Z"},"idempotency_key":{"type":"string","example":"idem_2026-05-30T18:00:00Z_run_abc123","description":"The Idempotency-Key header value the caller supplied."},"already_existed":{"type":"boolean","example":false,"description":"True when this exact submission was already recorded (idempotent replay). False when this submission caused a new row to be written."}},"required":["agent_type","run_id","client_id","biller_id","signal","submitted_at","idempotency_key","already_existed"]},"SubmitGenericFeedbackBodyDto":{"type":"object","properties":{"resource_type":{"type":"string","enum":["bill","account_connection","payment"],"example":"bill","description":"The kind of resource the feedback is about. The gateway resolves the resource to the agent run that produced it before recording feedback."},"resource_id":{"type":"string","example":"bill_abc123","description":"The id of the resource (bill id, account_link/connection id, or payment id). Ownership is enforced tenant-scoped against the calling client."},"signal":{"type":"string","example":"wrong_amount","description":"The client-vocabulary signal. Valid values depend on resource_type. Bill: bill_correct | wrong_amount | wrong_due_date | wrong_status | duplicate_bill | not_my_bill | missing_line_items | stale_data. Account connection: accounts_correct | wrong_account | missing_account | wrong_biller_matched | duplicate_account."},"idempotency_key":{"type":"string","example":"idem_2026-06-22T18:00:00Z_bill_abc123","description":"Stripe-style idempotency key. Required so retries do not double-count. Recommended format: ULID or `idem_<timestamp>_<resource_id>`."},"notes":{"type":"string","example":"Bill shows $0 but my paper bill says $84.32. Acct ending 1234.","description":"Free-text notes. PII-safe storage required; never echoed in metrics."}},"required":["resource_type","resource_id","signal","idempotency_key"]},"GenericFeedbackResponseDto":{"type":"object","properties":{"resource_type":{"type":"string","example":"bill"},"resource_id":{"type":"string","example":"bill_abc123"},"agent_type":{"type":"string","example":"recording_agent"},"signal":{"type":"string","example":"wrong_amount"},"attributable":{"type":"boolean","example":true,"description":"True when the feedback was attributed to a known agent run (run_id present + trace exists). False when the producing run is unknown / aged-out — the feedback is still recorded as observed/unattributable (D11 soft-accept) and returned with 202."},"run_id":{"type":"string","example":"run_abc123","description":"The agent run id the feedback was attributed to. Omitted when attributable=false."},"already_existed":{"type":"boolean","example":false,"description":"True when this exact submission was already recorded (idempotent replay)."}},"required":["resource_type","resource_id","agent_type","signal","attributable","already_existed"]},"CreateBrainNoteDto":{"type":"object","properties":{}},"BrainEntryKeyDto":{"type":"object","properties":{}},"AttentionSummaryResponseDto":{"type":"object","properties":{"generated_at":{"type":"string","description":"ISO timestamp the summary was computed at"},"unhealthy_script_billers":{"type":"number","nullable":true,"description":"Billers with at least one unhealthy recorded script (red / breakage)"},"manual_capture_billers":{"type":"number","nullable":true,"description":"Billers in the manual-capture lane (amber / review-pending)"},"dead_letter_count":{"type":"number","nullable":true,"description":"Webhook dead letters in status DEAD (red / breakage)"},"pending_human_note_hints":{"type":"number","nullable":true,"description":"Pending human_note brain drafts awaiting admin approval (amber / review-pending)"},"open_human_tasks":{"type":"number","nullable":true,"description":"Open tasks in the HUMAN executor lane (neutral / queue depth)"}},"required":["generated_at","unhealthy_script_billers","manual_capture_billers","dead_letter_count","pending_human_note_hints","open_human_tasks"]},"AnomalyCardDto":{"type":"object","properties":{"id":{"type":"string","description":"Stable card id: `${rule_id}:${entity}`"},"rule_id":{"type":"string","description":"Rule that produced the card","enum":["unhealthy_scripts","manual_capture","webhook_dead_letters","task_sla_breach","no_recent_bills","cost_spike"]},"severity":{"type":"string","description":"Card severity","enum":["error","warning","info"]},"title":{"type":"string","description":"The anomaly (e.g. \"Eversource: 0 new bills in 7d\")"},"cause":{"type":"string","description":"Attributed cause (e.g. \"RETRIEVE_BILL script unhealthy\")"},"link_href":{"type":"string","description":"Deep link to the admin screen that fixes it"},"link_label":{"type":"string","description":"Link button label"},"entity_id":{"type":"string","nullable":true,"description":"Primary entity id (null for fleet-wide cards)"},"observed_at":{"type":"string","nullable":true,"description":"ISO timestamp anchoring \"since when\""}},"required":["id","rule_id","severity","title","cause","link_href","link_label","entity_id","observed_at"]},"StubbedRuleDto":{"type":"object","properties":{"rule_id":{"type":"string","description":"Registry rule id awaiting an upstream unit"},"marker":{"type":"string","description":"Why it is stubbed (e.g. \"post-A12\")"}},"required":["rule_id","marker"]},"AnomalyFeedResponseDto":{"type":"object","properties":{"generated_at":{"type":"string","description":"ISO timestamp the feed was computed at (60s cache)"},"cards":{"description":"Attention cards sorted by severity (error → warning → info)","type":"array","items":{"$ref":"#/components/schemas/AnomalyCardDto"}},"sources_unavailable":{"description":"Source slots whose fetch failed this round (their rules were skipped)","type":"array","items":{"type":"string"}},"stubbed_rules":{"description":"Registry rules waiting on an upstream unit (e.g. cost_spike → post-A12)","type":"array","items":{"$ref":"#/components/schemas/StubbedRuleDto"}},"all_clear":{"type":"boolean","description":"True when there are no cards and every source was reachable"}},"required":["generated_at","cards","sources_unavailable","stubbed_rules","all_clear"]},"SaveLoginScriptDto":{"type":"object","properties":{}},"SaveAccountDiscoveryScriptDto":{"type":"object","properties":{}},"SaveRetrieveBillScriptDto":{"type":"object","properties":{}},"SaveRetrieveObservedPaymentScriptDto":{"type":"object","properties":{}},"SavePayBillScriptDto":{"type":"object","properties":{}},"ReportDrillFailureDto":{"type":"object","properties":{}},"ParkAwaitingCredentialsDto":{"type":"object","properties":{}},"ReportSelfHealEventDto":{"type":"object","properties":{}},"RecordRouteOutcomeDto":{"type":"object","properties":{}},"RecordProviderSignalsDto":{"type":"object","properties":{}},"RecordVerifyOutcomeDto":{"type":"object","properties":{}},"SubmitDocsFeedbackDto":{"type":"object","properties":{}},"MintTicketDto":{"type":"object","properties":{}}}},"externalDocs":{"description":"Developer Portal","url":"https://docs.billerapi.com"}}