ForVoyez:AI 驱动的图片元数据生成 SaaS 平台
For-Hives/ForVoyez
ForVoyez 是一个 SaaS 平台,通过 API 自动为图片生成 SEO 友好的 alt 文本、标题和说明文字,并以可自定义的 JSON 格式返回,支持 JPEG、PNG、WebP、GIF 格式。
ForVoyez - AI-Powered Image Metadata Generation
ForVoyez is a powerful SaaS platform that automatically generates SEO-optimized alternative text, titles, and captions for images using advanced AI technology. Our API processes your images and returns metadata in a customizable JSON format, making it easy to integrate into your existing workflows and boost your search engine rankings.
Features
- 🚀 Automatic generation of SEO-friendly alt text, titles, and captions
- 🎨 Support for various image formats (JPEG, PNG, WebP, GIF)
- 📊 Customizable JSON schema for tailored metadata output
- 🔒 Secure API access with generated tokens
- 📈 Detailed usage statistics and monitoring
- 💸 Flexible pricing plans to suit your needs and scale with your business
- 🧪 Interactive API playground for testing and experimentation
- 📚 Comprehensive documentation and code examples
Stats of the project
Getting Started
To get started with ForVoyez, follow these steps:
- Sign up for an account at forvoyez.com/sign-up.
- Choose a pricing plan that suits your needs.
- Generate an API token in your dashboard.
- Integrate the ForVoyez API into your application using the provided documentation and code examples.
- Start generating SEO-optimized image metadata and boost your search engine rankings!
Prerequisites
To run the ForVoyez project locally, ensure you have the following dependencies installed:
- Node.js:
- v24 recommended (v24.15 or higher), as in the Docker image and CI.
- v22.22.2 or higher (or v24.15 or higher) for the development tooling: the unit tests (jsdom 30), the pre-commit hook (lint-staged 17) and ESLint 10 need it.
- v22.12 or higher is enough to build and run the app (
pnpm build,pnpm start);pnpm prisma:seedandnode check-db-connection.jsload the generated TypeScript Prisma client directly and need v22.18 or higher. .nvmrcpins major 22 for the Nixpacks build on Coolify, whose default would be Node 18 (too old for Next 16 and Prisma 7); aNIXPACKS_NODE_VERSIONvariable on the Coolify app overrides it.
- pnpm (the version pinned in
package.jsonpackageManager, enable it withcorepack enable). On Node 22.12 and 22.13, the bundled corepack cannot fetch pnpm (Error: Cannot find matching keyid): runnpm i -g corepack@latestfirst. - PostgreSQL (v16.x or higher)
Development
To set up the development environment for ForVoyez, follow these steps:
You can use the following Docker command to set up a PostgreSQL database for local development:
docker run --name forvoyez-postgres -e POSTGRES_USER=forvoyez -e POSTGRES_PASSWORD=forvoyez -e POSTGRES_DB=forvoyez -p 5432:5432 -d postgres
If you want to configure your .env file to connect to the database, you can use the following environment variables:
DATABASE_URL="postgresql://forvoyez:forvoyez@localhost:5432/forvoyez"The app reads DATABASE_URL the way Prisma 6 did (src/helpers/databaseUrl.js), so a production URL keeps working unchanged: without sslmode (or with sslmode=prefer, or a value Prisma 6 did not know such as verify-full), TLS is used when the server offers it and plaintext otherwise; sslmode=require needs TLS; sslmode=disable turns it off. The certificate is only verified with sslaccept=strict, against the CA file given in sslcert (relative to prisma/) when there is one. Add uselibpqcompat=true to use node-postgres' own reading of the libpq parameters instead (sslmode=verify-full&sslrootcert=...).
Then, you can connect to the database using the following command:
- Clone the repository:
git clone https://github.com/For-Hives/ForVoyez.git - Navigate to the project directory:
cd forvoyez - Install dependencies:
pnpm install - Set up the PostgreSQL database using the provided Docker command (see README for details).
- Create a
.envfile based on the.env.examplefile and fill in the required environment variables. - Generate Prisma client:
pnpm installalready does it (postinstall), runpnpm prisma:generateagain after changingprisma/schema.prisma(the client is generated intosrc/generated/prisma, andpnpm buildruns this step too) - Run database migrations:
pnpm prisma:migrate(Prisma readsDATABASE_URLthroughprisma.config.ts, which loads.env) - Start the development server:
pnpm dev
Comparing AI models
scripts/compare-models.mjs runs the previous pipeline (3 sequential gpt-4o-mini calls, kept in scripts/legacy-image-description.mjs) and the current one (a single vision call with structured output, src/services/imageDescription.service.js) on every image of a folder, then writes a side-by-side report with the generated fields, the token usage and the latency. It calls the OpenAI API (4 calls per image), so try it on a small folder first:
node --env-file=.env scripts/compare-models.mjs ./my-images --context "Wedding photos" --language frIt needs OPENAI_API_KEY, accepts .jpg, .jpeg, .png, .webp and .gif, and writes compare-models-report/report.md and report.json (--out to change the folder). --model, --legacy-model and --detail override the compared models and the image detail; --help lists every option.
Webhook Configuration for Local Development
To test and develop the Lemonsqueezy webhook locally, follow these steps:
Prerequisites
- Ensure you have Node.js installed on your machine.
- Verify that you have set the required environment variables in your
.envfile, particularlyLEMON_SQUEEZY_WEBHOOK_SECRET, which should contain your Lemonsqueezy webhook secret key.
Steps
-
Go to https://webhook.site/ and copy your unique URL.
-
Start your local development server by running the following command in the terminal at the root of your project:
npm run devorbun dev
This will start your Next.js server, which will listen onhttp://localhost:3000by default. -
Globally install the
@webhooksite/clipackage by running the following command:npm install -g @webhooksite/cliThis will install the Webhook.site command-line tool, which will allow you to forward webhooks to your local server.
-
Go to the Lemonsqueezy account settings and configure the webhook URL to point to the unique URL provided by Webhook.site.
-
Run the following command to forward webhooks to your local server:
whcli forward --token=<your-token> --target=http://localhost:3000Replace
<your-token>with the token provided by Webhook.site. This command will forward the received webhooks from Webhook.site to your local/api/webhookendpoint. -
Configure the webhook URL in your Lemonsqueezy account settings to point to the unique URL provided by Webhook.site.
-
Perform an action in Lemonsqueezy that will trigger a webhook (e.g., a test payment).
-
Check your development server logs. You should see the details of the received webhook, indicating that your local endpoint has successfully processed the request.
Use ngrok for Local Development as an Alternative to Webhook.site
you can use ngrok to create a secure tunnel to your local server and receive webhooks from Lemonsqueezy. Follow these steps:
- Download and install ngrok from https://ngrok.com/download.
- Start your local development server by running the following command in the terminal at the root of your project:
npm run devorbun devThis will start your Next.js server, which will listen onhttp://localhost:3000by default. - Go to ngrok's admin interface on the website https://dashboard.ngrok.com/get-started/setup and copy your unique URL.
- You can create a static domain on the interface and use it for your webhook.
- Go to the Lemonsqueezy account settings and configure the webhook URL to point to the unique URL provided by ngrok.
- Run the following command to forward webhooks to your local server:
This command will create a secure tunnel to your local server, and you will receive a unique URL that you can use to forward webhooks to your local server.ngrok http --domain=[yourdomain] 3000 - Perform an action in Lemonsqueezy that will trigger a webhook (e.g., a test payment).
Troubleshooting
-
If you encounter errors related to HTTPS when forwarding webhooks to your local server, make sure to use
http://instead ofhttps://in the target URL. -
If your local server is not responding, verify that it is running and listening on the correct port (by default,
3000for Next.js). You can also check the server logs for any relevant error messages.
Failed or repeated webhook events
Every signed event is stored in the WebhookEvent table and answered with 200, even when processing fails, so Lemon Squeezy does not retry it.
- A processing failure is logged as
webhook <id> (<event>) processing failedand its message is stored inWebhookEvent.processingError(processedstaysfalse). For examplePlan not found for variant ...means the plans must be synced first (GET /api/sync). - To replay a failed event once the cause is fixed, resend it from the Lemon Squeezy dashboard (Settings > Webhooks). Find the failed ones with
SELECT id, "eventName", "processingError" FROM "WebhookEvent" WHERE processed = false AND "processingError" IS NOT NULL;. - A plan change comes as a
subscription_updatedevent whosevariant_idis not the subscription's current plan (Lemon Squeezy sendssubscription_updatedfor every change of a subscription, and nosubscription_plan_changedwas ever received in production; one would be applied the same way, and when both arrive the second one changes nothing). The subscription moves to the new plan and keeps the previous one inoldPlanId; the event is markedprocessedwith aPlan change applied: plan <old> -> plan <new>note. Asubscription_updatedolder (updated_at) than one already processed for the same subscription does not move it back. - Lemon Squeezy does not order its webhooks: the invoice of an upgrade (
subscription_payment_success, billing reasonupdated, creditedmax(0, new plan - old plan)) processed before its plan change fails withplan change not processed yet. Resend it once the plan change is processed. The plan change must be a processedsubscription_updatedthat changed the plan (with the note above) orsubscription_plan_changedto the subscription's current plan: anoldPlanIdleft by the previous version of the app (which never cleared it and never marked events processed) is not taken for it. - Events that add credits (
order_created,subscription_payment_success) are credited once per Lemon Squeezy order or invoice (data.id): a redelivery of an already processed one is stored, markedprocessedwith aDuplicate of webhook event <id>note, and not credited again.
Image Metadata Generation Process
ForVoyez uses an OpenAI vision model (FORVOYEZ_AI_MODEL, see Environment Variables) to generate image metadata, in a single call per image (src/services/imageDescription.service.js):
- The user sends an image file to
/api/describe(or the playground), with an optional context, keywords, language and output schema. - The image is checked (10 MB maximum; JPEG, PNG, WebP or GIF, read from the file itself, not from the MIME type it was sent with), turned upright and resized to fit 1000x1000 pixels.
- The image, the context, the keywords and the requested fields are sent to the model in one request, which must answer with structured output: exactly the requested keys, each a string.
- The generated metadata is returned as a flat JSON object. One credit is charged only when the generation succeeds.
API Usage Tracking
Users can monitor their API usage and quota limits directly through the ForVoyez web interface. The dashboard provides detailed statistics and charts showing the number of API calls made, credits consumed, and remaining quota for the current billing period.
Bug Reports and Feature Requests
If you encounter any bugs, have feature requests, or want to contribute to the project, please email us at support@forvoyez.com. Our team will assist you and provide guidance on how to report issues or submit pull requests.
API Usage
The full reference is at doc.forvoyez.com. In short, send a multipart/form-data POST request to https://forvoyez.com/api/describe with an API key from the dashboard (Authorization: Bearer <YOUR_API_TOKEN>) and the following fields:
image: the image file to process (JPEG, PNG, WebP, GIF, 10 MB maximum). The format is read from the file, so its MIME type does not matter (curl -F image=@photo.webpsendsapplication/octet-stream); another format, such as SVG, AVIF or TIFF, gets a 400Bad Request, Invalid image file.context(optional): additional information about the image to guide the generation.keywords(optional): keywords to work into the metadata.language(optional): language of the generated metadata,enby default. A language code (it,pt-BR,he_IL) is given to the model by its name (Italian, Brazilian Portuguese, Hebrew (Israel)); any other value is passed as written.schema(optional): a JSON string, a flat map of output field name to description. Without it, the fields aretitle,alternativeTextandcaption, and the response also hasalt_text, a copy ofalternativeTextthat the WordPress plugin up to 1.1.40 reads.
Example Request:
curl -X POST -H "Authorization: Bearer <YOUR_API_TOKEN>" -F "image=@/path/to/image.jpg" -F "context=A beautiful sunset over the ocean" -F "language=en" https://forvoyez.com/api/describe
Example Response (200, exactly the schema keys, plus alt_text because this request sends no schema):
{
"title": "Serene Sunset Over the Calm Ocean Waves",
"alternativeText": "A breathtaking sunset with vibrant orange and pink hues reflected on the tranquil ocean surface, creating a peaceful and mesmerizing seascape.",
"caption": "Witness the enchanting beauty of a serene sunset over the calm ocean waves, as the vibrant colors paint the sky and the gentle breeze carries the salty scent of the sea.",
"alt_text": "A breathtaking sunset with vibrant orange and pink hues reflected on the tranquil ocean surface, creating a peaceful and mesmerizing seascape."
}Errors keep their HTTP status (400 invalid request, 401 missing, invalid, revoked or expired API key, or no credit left, 413 image over 10 MB, 500 server error) and have a JSON body: { "error": "<human message>" }. An image over 10 MB, or a request body over 11 MB, gets a 413 Image too large: the maximum is 10 MB; a body that is not multipart/form-data gets a 400. No credit is charged for any error. A schema whose fields need more than about 1,500 words in total gets a 400 Invalid schema: the requested fields need a longer answer than the API can return ... (the generation stops at its output limit, the credit is refunded).
Environment Variables
To run the ForVoyez project, you need to set up the environment variables in a .env file.
You must follow the .env.example file to define the required variables.
Make sure to replace the placeholders with your actual values for each environment variable.
Optional variables:
FORVOYEZ_AI_MODEL: OpenAI model used by/api/describeand the playground (defaultgpt-6-luna, sameOPENAI_API_KEY). Set it togpt-5.6-luna(tied on quality in the October 2026 evaluation, about 2x the cost) orgpt-4o-mini(previous model) to roll back without a code change.FORVOYEZ_AI_IMAGE_DETAIL: OpenAI image detail sent with each image,low(default),autoorhigh.SYNC_SECRET: enablesGET /api/sync(copies the Lemon Squeezy products into thePlantable). Call it with the headerx-sync-secret: <SYNC_SECRET>(the former?true=truequery is no longer used); without the variable, or with a wrong header, the route answers 404. Run it after adding or changing a product or variant in Lemon Squeezy, otherwise purchases of that variant fail withPlan not found.
Support
If you encounter any issues, have questions, or need assistance, please don't hesitate to reach out to our support team at support@forvoyez.com or visit our contact page.
🌟 Boost your image SEO with ForVoyez - the ultimate AI-powered image metadata generation solution! 🌟
来源:GitHub · github.com