Add verifly.email API#2722
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the initial OpenAPI 3.1.0 specification for the Verifly Email Verification API, covering endpoints for onboarding, verification, job management, list cleaning, and billing. The review feedback highlights several key areas to improve the specification's accuracy and usability for client SDK generators. These include explicitly defining the X-Internal-Key header parameter, making authentication optional for checkout endpoints, adding a discriminator to the billing response, enforcing that at least one input field (emails or text) is provided in bulk and file processing requests, and aligning parameter types between JSON and multipart/form-data schemas.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| parameters: | ||
| - name: id | ||
| in: path | ||
| required: true | ||
| schema: | ||
| type: string |
There was a problem hiding this comment.
The description for the /api/v1/jobs/{id}/process endpoint mentions that it accepts an internal service key via the X-Internal-Key header. However, this header is not defined as a parameter in the OpenAPI specification for this endpoint. To ensure client generators and API documentation tools correctly recognize and support this header, it should be explicitly defined as a header parameter.
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: X-Internal-Key
in: header
required: false
schema:
type: string
description: Internal service key to authorize the request.| /api/checkout: | ||
| post: | ||
| tags: | ||
| - Billing | ||
| operationId: createStripeCheckout | ||
| summary: Create a Stripe checkout session (API key or session) | ||
| description: 'Create a hosted Stripe checkout session for a credit package. Accepts an API key (Authorization: Bearer vf_...) OR a logged-in browser session. Body accepts package_id or packageId.' |
There was a problem hiding this comment.
The description for /api/checkout states that it "Accepts an API key (Authorization: Bearer vf_...) OR a logged-in browser session." However, because there is no local security override, it inherits the global bearerAuth security requirement. This makes the Bearer token mandatory for all clients and tools (like Swagger UI or SDK generators). To correctly reflect that authentication is optional (e.g., when a browser session is used), you should override the security requirement locally to include an empty security object.
/api/checkout:
post:
tags:
- Billing
operationId: createStripeCheckout
summary: Create a Stripe checkout session (API key or session)
description: 'Create a hosted Stripe checkout session for a credit package. Accepts an API key (Authorization: Bearer vf_...) OR a logged-in browser session. Body accepts package_id or packageId.'
security:
- bearerAuth: []
- {}| /api/checkout/crypto: | ||
| post: | ||
| tags: | ||
| - Billing | ||
| operationId: createCryptoCheckout | ||
| summary: Create a crypto (Plisio) checkout (API key or session) | ||
| description: Create a Plisio crypto invoice for a credit package. Accepts an API key OR a logged-in browser session. Body accepts package_id or packageId and an optional currency (BTC/ETH/LTC/USDT/USDC); when currency is supplied the response includes a raw wallet address + amount + qr_code. |
There was a problem hiding this comment.
The description for /api/checkout/crypto states that it "Accepts an API key OR a logged-in browser session." However, because there is no local security override, it inherits the global bearerAuth security requirement. This makes the Bearer token mandatory for all clients and tools (like Swagger UI or SDK generators). To correctly reflect that authentication is optional (e.g., when a browser session is used), you should override the security requirement locally to include an empty security object.
/api/checkout/crypto:
post:
tags:
- Billing
operationId: createCryptoCheckout
summary: Create a crypto (Plisio) checkout (API key or session)
description: Create a Plisio crypto invoice for a credit package. Accepts an API key OR a logged-in browser session. Body accepts package_id or packageId and an optional currency (BTC/ETH/LTC/USDT/USDC); when currency is supplied the response includes a raw wallet address + amount + qr_code.
security:
- bearerAuth: []
- {}| schema: | ||
| oneOf: | ||
| - $ref: '#/components/schemas/StripeCheckoutResult' | ||
| - $ref: '#/components/schemas/CryptoCheckoutResult' |
There was a problem hiding this comment.
The response for /api/v1/billing POST uses oneOf to return either StripeCheckoutResult or CryptoCheckoutResult. Both schemas contain a method property with a constant value (stripe or crypto). Adding an explicit discriminator mapping on the method field will allow client SDK generators to produce clean, strongly-typed polymorphic classes/interfaces instead of generic union types.
schema:
oneOf:
- $ref: '#/components/schemas/StripeCheckoutResult'
- $ref: '#/components/schemas/CryptoCheckoutResult'
discriminator:
propertyName: method
mapping:
stripe: '#/components/schemas/StripeCheckoutResult'
crypto: '#/components/schemas/CryptoCheckoutResult'| BulkVerificationRequest: | ||
| type: object | ||
| properties: | ||
| emails: | ||
| type: array | ||
| minItems: 1 | ||
| maxItems: 1000000 | ||
| items: | ||
| type: string | ||
| maxLength: 254 | ||
| text: | ||
| type: string | ||
| description: Raw text/CSV to extract emails from when 'emails' is not provided. | ||
| filename: | ||
| type: string | ||
| maxLength: 255 | ||
| default: bulk_upload.csv | ||
| webhook_url: | ||
| type: string | ||
| format: uri | ||
| description: Optional public HTTPS webhook called when the job completes. | ||
| additionalProperties: false |
There was a problem hiding this comment.
In BulkVerificationRequest, both emails and text are marked as optional. This allows clients to send an empty JSON object {}, which is semantically invalid. Using anyOf at the schema level enforces that at least one of these input methods is provided, improving validation and client-side error prevention.
BulkVerificationRequest:
type: object
properties:
emails:
type: array
minItems: 1
maxItems: 1000000
items:
type: string
maxLength: 254
text:
type: string
description: Raw text/CSV to extract emails from when 'emails' is not provided.
filename:
type: string
maxLength: 255
default: bulk_upload.csv
webhook_url:
type: string
format: uri
description: Optional public HTTPS webhook called when the job completes.
anyOf:
- required: [emails]
- required: [text]
additionalProperties: false| FileProcessRequest: | ||
| type: object | ||
| properties: | ||
| emails: | ||
| type: array | ||
| items: | ||
| type: string | ||
| text: | ||
| type: string | ||
| options: | ||
| type: object | ||
| properties: | ||
| deduplicate: | ||
| type: boolean | ||
| default: true | ||
| exclude_public_domains: | ||
| type: boolean | ||
| remove_public: | ||
| type: boolean | ||
| exclude_role_accounts: | ||
| type: boolean | ||
| remove_role: | ||
| type: boolean | ||
| remove_disposable: | ||
| type: boolean | ||
| domain_blacklist: | ||
| type: array | ||
| items: | ||
| type: string | ||
| pattern_blacklist: | ||
| type: array | ||
| items: | ||
| type: string | ||
| verify: | ||
| type: boolean | ||
| default: false | ||
| description: Verify each email (costs credits). | ||
| save: | ||
| type: boolean | ||
| default: false | ||
| description: Save the result as a downloadable file. | ||
| filename: | ||
| type: string |
There was a problem hiding this comment.
Similar to BulkVerificationRequest, FileProcessRequest allows both emails and text to be omitted, which could result in an empty and invalid request. Adding an anyOf block ensures that at least one of these input fields is supplied.
FileProcessRequest:
type: object
anyOf:
- required: [emails]
- required: [text]
properties:
emails:
type: array
items:
type: string
text:
type: string
options:
type: object
properties:
deduplicate:
type: boolean
default: true
exclude_public_domains:
type: boolean
remove_public:
type: boolean
exclude_role_accounts:
type: boolean
remove_role:
type: boolean
remove_disposable:
type: boolean
domain_blacklist:
type: array
items:
type: string
pattern_blacklist:
type: array
items:
type: string
verify:
type: boolean
default: false
description: Verify each email (costs credits).
save:
type: boolean
default: false
description: Save the result as a downloadable file.
filename:
type: string| verify: | ||
| type: string | ||
| save: | ||
| type: string |
There was a problem hiding this comment.
In the /api/v1/files POST endpoint, the multipart/form-data schema defines verify and save as type: string. However, in the JSON-equivalent FileProcessRequest schema, these options are defined as type: boolean. To maintain consistency and ensure client SDKs generate correct boolean parameters for both content types, these fields should be defined as type: boolean in the multipart schema as well.
verify:
type: boolean
default: false
save:
type: boolean
default: false
Adds the Verifly Email Verification API (
verifly.email).Verifly is an agent-native, pay-as-you-go email verification API for developers, SaaS signup validation, bulk list cleaning, and autonomous AI agents. It checks deliverability and syntax, detects disposable / role / catch-all addresses, validates domain MX/DNS health, and exposes usage & credit endpoints.
APIs/verifly.email/2.0.0/openapi.yamlNotes for the maintainer:
2.0.0matchesinfo.versionin the spec.