An integration is one config record for one provider: data.provider names the provider class (SendGridProvider, ShippoProvider, OpenAIProvider…), data.status says whether it is in service, data.priority ranks it against others that can do the same job, and the rest of data holds that provider's settings and keys. The platform picks a config by what the job needs. Nothing points at a config by id.
Most services work on a new business with no config at all, on the platform's own account. Whose account a service runs on (the platform's, a sub-account, or the business's own), and what each costs, is explained in How integrations work and Your Appmint plan, credit, usage and services. The list of providers is the Integration catalog. The person's own walkthrough is Bring your own API credentials, gateway and email.
Use the job's own page first
Many services are connected on their own screen, with steps this page doesn't repeat:
| The job | Go to |
|---|---|
| Card payments, PayPal, pay later | Take payments on the site |
| Hosted mailboxes, a sending domain, the sender address | Business email and the sending address |
| Reading a support mailbox (IMAP, Microsoft 365) | Shared inbox |
| Carrier rates and labels | Shipping rates and settings |
| Facebook, Instagram and other social accounts | Social posting and inbox |
| Facebook lead ads | Inbound lead channels |
| Phone numbers and calls; SMS registration; WhatsApp | Business phone, SMS number registration, WhatsApp messaging |
| Amazon, eBay, Google Shopping and other sales channels | List a product on a sales channel |
| Turning a platform service on (its agreement) | Your Appmint plan, credit, usage and services |
| Google Analytics or the Meta Pixel | not an integration: Google Analytics and the Facebook Pixel |
Use this page for anything else, and to check, test, switch off or rank any config.
Ask first
- Which service, for what job? "Send our email through SendGrid", "use our OpenAI key for AI".
- Do they already have an account with that provider? The person signs in to it to get the keys. The AI never asks for a key, secret, token or password in chat, and never types one.
- Their own account, or the platform's? Without a config of their own, the platform's account is used and billed to their balance. Their own account moves the provider's bill to them.
- Test or live keys? Use test keys first where the provider has them.
- Is something of theirs already connected for this job? Check step 1 before adding a second.
Steps
1. Integrations: what is there now
List the business's configs with repo_find on config. Report only data.name, data.provider, data.status and data.priority.
A config record holds the keys. repo_find returns every field, secrets included. Never repeat, summarise or copy a key, secret or token out of it, and never put one in a message, a note or another record.
Whether each platform service is running on the business's own account or the platform's: call_appengine GET /usage/shared (account: "own" or "shared"). For payments: GET /storefront/payment-gateways, which shows public fields only.
On screen: Configuration › Integrations (/integration-config). It has three tabs: Configurations (the list, with Edit, View JSON, Test and Delete on each row), Integration Builder (every provider, to make a new config) and API Playground.
Hands on: what exists for the job, by name and status. Check: the person sees the same rows on Configurations.
2. Pick the provider
call_appengine GET /upstream/integration-types lists every provider (57 on a current engine). Each one has:
| Field | What it tells you |
|---|---|
name | The provider class, e.g. SendGridProvider. This goes in data.provider. |
type | Email, Payment, AI, Database, SocialMedia, Marketplace, GOOGLE, MICROSOFT or Integration |
useCases | The jobs it can do, e.g. ["Email", "Transactional Email", "Marketing"] |
configSchema | Its settings form: each field's title, and required for the ones it must have. format: "password" marks a secret. |
operation | What it can be asked to do (sendEmail, getRates, test…). A config is used for a job only if its provider lists that operation. |
Tell the person which fields they will need from the provider before they start, from configSchema.required.
Hands on: the provider name.
3. Setup wizard or Integration Builder: the person connects it
Stop: this is the person's to do. They enter the credentials on the screen. Send them there with the provider named.
- Setup wizard (
/setup-wizard) has its own connect flow for Stripe, PayPal, Twilio, Facebook, Google, TikTok, Pinterest, LinkedIn and X (Twitter). Most are a sign-in to the provider rather than a key to paste. Each also has Or enter API credentials manually. Use the wizard for these. - For everything else: Integrations › Integration Builder › search for the provider › fill in its form › Save. The form is the provider's
configSchemaplus the config's own fields (name, priority, status).
What the builder saves:
data.name:"<Provider> Configuration"until changed. Give it a clear, unique name (sendgrid-live). The running copy of an integration is kept under provider plus name, so two configs for one provider need different names.data.status:inactive. A new config from the builder is never live on save.- the provider's fields as typed.
An inactive config switches that job off, the platform's account included. While a business has configs for a provider or a job and none of them is active or use-platform, the platform's own account is not used for it either (appengine upstream/upstream.service.ts: leavesPlatformOpen, mayUsePlatform, getIntegrationByOperation). So a SendGrid config saved and left inactive stops the business's email going out through the platform. The same goes for an AI key, a carrier, SMS. Finish step 5 the same day, or delete the config.
Hands on: the config's id (sk), once saved.
Check: it is on Configurations with status inactive.
4. Test it
Testing runs the provider's own test operation against the provider with these credentials. For most providers it only checks that the credentials are accepted; it doesn't send, charge or post.
- On screen: the row's Test on Configurations, or Test in the builder. It sends the form's values as they are, saved or not, whatever the status.
- For the AI:
call_appenginePOST /upstream/call/<provider name>/testwith{ "data": null, "config": <the config's data> }. Buildconfigfrom the record you read in step 1 and send it only in this call.
What comes back is the provider's own answer, not just a status code. Tested on a local engine:
| Request | Answer |
|---|---|
POST /upstream/call/OfflineProvider/test with the config's data | { "success": true, "message": "Offline payment provider is ready", … } |
POST /upstream/call/<config id>/test with {}, on an inactive config | 404 not_found. By id only active configs are used. |
POST /upstream/call/<provider name>/test with {}, with no active config of the business's own | 404 not_found, or the platform's config is tested instead when the platform has one. Always send config. |
A test can fail with HTTP 200: read the body. error, success: false, or status: "failed" is a failure. A wrong key typically reads like 401 API key is invalid; an account problem like 429 You have no credits remaining. Say which one it is: the key, the account, or the settings.
Use /upstream/call/…/test. The older POST /upstream/test/<provider>/<operation> route fails before it reaches the provider, whatever is sent: tested, it answers 400 Cannot read properties of undefined (reading 'data') (appengine upstream/upstream.service.ts 897–923).
Hands on: the test's answer. Check: a passing test says so in the provider's words. Don't call a config working on a save alone.
5. Integrations: switch it on
Stop: making it active moves that job onto the business's own account straight away: the next email, label or AI call goes through it and is billed by the provider. Say which jobs move and wait for yes.
The person sets Status on the config's form and saves. Or the AI, after the yes, uses repo_update on config with only the fields that change, e.g. patch: { "status": "active" }. repo_update keeps every other field (the keys stay put). Never resend the whole record to change one field.
data.status | What it means |
|---|---|
active | In service. A config with no status counts as active, so one written without a status is live at once. |
inactive | Switched off. It blocks the platform's account for the job too (step 3 warning). |
deprecated | Retired. Treated like inactive. |
use-platform | Keep the record and its keys, but run the job on the platform's account. The way back from "ours" to "yours" without deleting anything. |
| Field | What it does |
|---|---|
data.priority | 0–100, 1 when unset. When several active configs can do the same job, the highest runs. It is the only thing that picks between them. |
data.sendOutgoing | On mail providers. false means this config never sends the system's outgoing mail. Unset means yes. |
data.syncIncoming | On mailbox providers (IMAP, Microsoft). false means its mail isn't read in. Unset means yes. |
A save or delete takes effect on the next call: the running copy of that integration is dropped and rebuilt from the new record. There is nothing to restart.
Hands on: the status and priority as saved.
Check: repo_find_one on the config shows data.status and data.priority as asked. Then try the job once: the person sends themselves a test email, quotes a label, asks the AI something. GET /usage/shared now shows that service on account: "own".
Switching off, going back, removing
| They want | Do |
|---|---|
| Stop using their account, carry on with the platform's | status: "use-platform" |
| Stop the job altogether | status: "inactive" (and know it also stops the platform's) |
| Remove it | Delete on the row, or repo_delete on config. With no config left, the platform's account is used again. Deleted configs don't go to Trash. |
Each is a Stop: it changes how the business's email, payments or AI run from the next call.
When it's done
Tell the person:
- the provider, the config's name, and whether it is test or live;
- what the test said, in the provider's words;
- its status and priority, and which jobs now run on their account;
- where to change it: Configuration › Integrations › Configurations.
What it can't do yet
- No connection status on its own. A config shows only its saved status. Whether the keys still work is known only by testing (step 4) or by a job failing.
- A new config can quietly switch a job off while it is inactive (step 3 warning).
POST /upstream/test/…doesn't work (step 4 note). Use/upstream/call/<provider>/test.- No arbitrary API. Only the providers in the list can be connected. Your own service needs a provider built for it, or a server of your own calling Appmint: see Bring your own API credentials.
- Config names aren't checked. Spaces and duplicates are accepted on save (tested). Keep names unique per provider yourself.
Don'ts
- Don't ask for, type, paste or repeat a key, secret, token or password. The person enters them on the screen.
- Don't leave a new config inactive "for later". It turns that job off.
- Don't write a config without
status. It goes live at once. - Don't resend a whole config to change its status. Patch the one field.
- Don't call a config working until its test passes and the job has been tried once.
- Don't connect a service here when the job has its own page (the table at the top).