/v1/me
Describe this credential
Credential type (OAuth access token or API key), the user it acts as, its scopes and the business ids it is connected to.
Read and write customers, quotes, invoices and payments, and get signed webhooks when they change. Your users connect their iQuote businesses with one click; no passwords change hands.
A JSON API over HTTPS. Plain HTTP is refused, not redirected. Every request is made on behalf of a person who is an owner or admin of the business, and runs under the same rules as the app: numbering, totals, deposits, plan limits and who may move money.
The API is part of the Pro, Multi-Business, Enterprise, Infinity plans. A business on Free or Starter answers 402 plan_required.
https://i-quote.app/api/v1| Topic | Detail |
|---|---|
| Authentication | Authorization: Bearer with an OAuth access token iqat_… or an API key iqk_…. Passwords and iQuote session tokens are never accepted. |
| Business | IQuote-Business: <business id> on each request when a connection covers several businesses. |
| Format | JSON bodies (Content-Type: application/json, up to 1 MB). Amounts are numbers in the document currency; dates are YYYY-MM-DD or ISO 8601. |
| Ids | UUIDs. Every object carries id and object (customer, quote, invoice, payment …). |
| Spec | https://i-quote.app/openapi.json (OpenAPI 3.1, includes webhooks). |
curl https://i-quote.app/api/v1/me \
-H "Authorization: Bearer $IQUOTE_TOKEN"For apps used by many iQuote businesses. The authorization code flow with PKCE (S256), required for every client. The user signs in on iQuote itself and approves what your app may do; you never see their password.
Redirect the browser to https://i-quote.app/oauth/authorize with response_type=code, your client_id, a registered redirect_uri (matched exactly), the scopes you need (space-separated; leave scope out to ask for all your app is allowed), a random state, and code_challenge with code_challenge_method=S256.
They sign in to iQuote, pick one or several of their businesses to connect, and approve the scopes. Only businesses where they are an owner or admin on Pro or higher can be picked.
Your redirect_uri receives code, state and iss. Check that state is the one you sent and iss is https://i-quote.app. A declined request comes back with error=access_denied. The code works once, for 5 minutes.
POST to https://i-quote.app/oauth/token with grant_type=authorization_code, the code, the same redirect_uri and the code_verifier. Form-encoded or JSON. Confidential clients authenticate with HTTP Basic or client_id and client_secret in the body; public clients send client_id only. The answer lists the connected businesses as businesses: [{ id, name }].
Send Authorization: Bearer <access_token> and, when several businesses are connected, IQuote-Business: <business id>. Access tokens last one hour.
grant_type=refresh_token returns a new access token and a new refresh token (30 days). Refresh tokens rotate: using one twice revokes the whole connection, so store the new one every time. To disconnect, POST the refresh token to https://i-quote.app/oauth/revoke (RFC 7009). Users can also disconnect your app from iQuote at any time.
# The authorize step happens in the user's browser, not with curl.
# Generate a verifier and its S256 challenge in a shell:
CODE_VERIFIER=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
STATE=$(openssl rand -hex 16)
# Then open this URL in a browser (scope is space-separated, %20 encoded):
echo "https://i-quote.app/oauth/authorize?response_type=code&client_id=$IQUOTE_CLIENT_ID&redirect_uri=https%3A%2F%2Fyourapp.example%2Fcallback%2Fiquote&scope=customers%3Aread%20invoices%3Aread%20invoices%3Awrite&state=$STATE&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256"# Back on your redirect_uri: ?code=iqac_...&state=...&iss=https://i-quote.app
# Check that state is the one you stored and iss is https://i-quote.app, then:
curl https://i-quote.app/oauth/token \
-u "$IQUOTE_CLIENT_ID:$IQUOTE_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
-d redirect_uri=https://yourapp.example/callback/iquote \
-d code_verifier="$CODE_VERIFIER"
# Public client (no secret): drop -u and send -d client_id="$IQUOTE_CLIENT_ID"curl https://i-quote.app/oauth/token \
-u "$IQUOTE_CLIENT_ID:$IQUOTE_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN"
# Optional: -d scope="invoices:read" to narrow the new access token.
# Save the NEW refresh_token from the answer. The old one is now spent.
# Disconnect (RFC 7009). A refresh token ends the whole connection:
curl https://i-quote.app/oauth/revoke \
-u "$IQUOTE_CLIENT_ID:$IQUOTE_CLIENT_SECRET" \
-d token="$REFRESH_TOKEN"Access stays tied to the user. If they stop being an owner or admin of a business, or the business leaves Pro or higher, calls for it fail (403 or 402) even with a valid token. GET /v1/businesses shows the state of each connected business.
An owner or admin of a business on Pro or higher registers apps in Settings → Developers. You get a client_id (iqc_…) and, for a confidential client, a client_secret (iqcs_…) that is shown once; rotate it there if it leaks.
Confidential clients run on a server that can keep a secret. Public clients (mobile, desktop, single-page apps) have no secret and rely on PKCE alone.
Redirect URIs must be https, or http on localhost, 127.0.0.1 or [::1] for development. They are matched exactly: no wildcards, no fragments. Choose the scopes the app may ask for; a request for any other scope is refused.
The consent page shows users your app name, description, homepage and the business that registered it.
For your own scripts and integrations on one business. An owner or admin creates a key in Settings → Developers, chooses its scopes and, optionally, when it expires. The key (iqk_…) is shown once. It acts as the person who created it, so it stops working if they lose owner or admin rights, and it can be revoked at any time.
A key belongs to exactly one business, so the IQuote-Business header is not needed. Keep keys on a server; never ship one in a browser or mobile app (use Connect with iQuote there).
A write scope does not include read: ask for both when you need both. The wording is what users see on the consent page.
| Scope | Allows |
|---|---|
customers:read | See your customers |
customers:write | Create, change and delete customers |
quotes:read | See your quotes |
quotes:write | Create, change, send and convert quotes |
invoices:read | See your invoices and reports |
invoices:write | Create, change, send and void invoices, send reminders and record payments received |
refunds:write | Refund card payments to your customers |
payments:read | See payments received |
webhooks:manage | Manage webhook endpoints that receive your business events |
GET /v1/me and GET /v1/businesses need no scope. GET /v1/reports/summary needs invoices:read and adds quote figures when the token also has quotes:read.
One OAuth connection can cover several businesses: the user picks them on the consent page, and the token response lists them. Name the business on each request with the IQuote-Business header (or ?business_id=). With only one connected business it can be left out.
Leaving it out when several are connected answers 400 business_required with the connected ids in error.business_ids. A business the connection does not cover answers 403 business_not_connected.
# Which businesses does this connection reach?
curl https://i-quote.app/api/v1/businesses \
-H "Authorization: Bearer $IQUOTE_TOKEN"
# Overdue invoices of one of them, 50 per page
curl "https://i-quote.app/api/v1/invoices?status=overdue&limit=50" \
-H "Authorization: Bearer $IQUOTE_TOKEN" \
-H "IQuote-Business: $BUSINESS_ID"
# Next page: add &cursor=<next_cursor from the previous answer>120 requests per minute per access token or API key, and 600 per minute per business across every app and key. Over the limit, the answer is 429 rate_limited with a Retry-After header in seconds.
Every authenticated answer carries X-RateLimit-Limit and X-RateLimit-Remaining. Sending quote, invoice and reminder emails also counts against the business’s daily email budget (429 email_rate_limited).
Send an Idempotency-Key header (1-255 printable characters, e.g. a UUID) on POST, PATCH and DELETE. For 24 hours, repeating the request with the same key returns the first answer with Idempotent-Replayed: true instead of doing the work again: no second invoice, no second email.
While the first request is still running, a repeat answers 409 idempotency_in_progress. The same key with a different method, path or body answers 422 idempotency_key_reused. Keys are scoped to the credential and business. Server errors (5xx) are not stored, so retrying them runs the request again.
curl https://i-quote.app/api/v1/invoices \
-H "Authorization: Bearer $IQUOTE_TOKEN" \
-H "IQuote-Business: $BUSINESS_ID" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-invoice" \
-d '{
"customer_id": "6f1c2b3a-4d5e-4f60-8a7b-9c0d1e2f3a4b",
"currency": "USD",
"due_date": "2026-11-15",
"line_items": [
{ "description": "Website redesign", "quantity": 1, "unit_price": 2400 },
{ "description": "Hosting (monthly)", "quantity": 1, "unit_price": 30 }
],
"deposit": { "type": "percent", "value": 30 }
}'
# Same key again within 24 hours: the first answer is replayed
# (Idempotent-Replayed: true) and no second invoice is created.
# Then email it: curl -X POST https://i-quote.app/api/v1/invoices/<id>/send ... -H "Idempotency-Key: order-1042-send"Lists are newest first. limit sets the page size (1-100, default 25). When has_more is true, pass next_cursor as cursor to get the next page. Cursors are opaque: do not build them yourself.
{
"object": "list",
"data": [ { "id": "...", "object": "invoice", ... } ],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0xMC0xNlQwOToyMDowMC4wMDBaIiwiaSI6Ii4uLiJ9"
}Errors use HTTP status codes and one JSON shape. Branch on error.code, which is stable; error.message is for people. Quote error.request_id (also the IQuote-Request-Id header) when you contact support.
HTTP/1.1 403 Forbidden
IQuote-Request-Id: req_8f2a1c9d4b7e6a3f0c5d2e1b
{
"error": {
"type": "permission_error",
"code": "insufficient_scope",
"message": "This token is missing the invoices:write scope",
"required_scope": "invoices:write",
"request_id": "req_8f2a1c9d4b7e6a3f0c5d2e1b"
}
}| Status | error.type | error.code | Meaning |
|---|---|---|---|
| 400 | invalid_request | invalid_json, missing_field, invalid_value, unknown_field, invalid_id, invalid_limit, invalid_cursor, invalid_business, business_required, invalid_idempotency_key, invalid_line_items, too_many, amount_too_high, invalid_url, invalid_events, refund_refused, idempotency_key_required, invalid_path | The request is malformed or a value is out of range. param names the field when there is one. refund_refused: Stripe is not connected, or nothing on the invoice can be refunded. |
| 401 | authentication_error | missing_token, invalid_token | No credential, or one that is unknown, expired or revoked. Refresh the access token or reconnect. |
| 402 | plan_error | plan_required, plan_limit_reached | The business is not on Pro or higher, or a plan limit was reached. |
| 403 | permission_error | https_required, insufficient_scope, business_not_connected, not_a_member, insufficient_role, business_blocked, forbidden | The credential may not do this: a missing scope, a business it is not connected to, a user who is no longer an owner or admin, or a suspended business. |
| 404 | not_found | route_not_found, not_found, business_not_found | No such endpoint, or no such object in this business. |
| 405 | invalid_request | method_not_allowed | The path exists but not with this method. |
| 409 | conflict | idempotency_in_progress, conflict, duplicate_email, duplicate_external_id, customer_has_history, quote_not_editable, quote_not_draft, quote_not_sendable, quote_not_convertible, customer_no_email, invoice_not_editable, invoice_not_draft, invoice_has_payments, invoice_not_sendable, invoice_not_payable, invoice_not_voidable, not_recurring, card_payment, too_many_endpoints, business_suspended, business_locked | The object is not in a state that allows this, the same rules the app applies. |
| 413 | invalid_request | body_too_large | The body is larger than 1 MB. |
| 415 | invalid_request | unsupported_media_type | Send JSON with Content-Type: application/json. |
| 422 | invalid_request | idempotency_key_reused, invalid_reference, invalid_value, rule_violation | Well-formed but refused: an Idempotency-Key reused with a different request, a reference to an object of another business, or a database rule. |
| 429 | rate_limit_error | rate_limited, email_rate_limited, refund_rate_limited | Too many requests, the business reached its daily email budget, or its hourly API refund limit. Wait for Retry-After. |
| 500 | api_error | internal_error, database_error, partial_failure, convert_failed, refund_failed | Something failed on our side. Retrying with the same Idempotency-Key is safe. |
| 502 | upstream_error | email_failed, stripe_error | The email provider or Stripe refused or failed. Nothing was marked sent or refunded. |
| 503 | service_unavailable | not_configured, auth_unavailable, idempotency_unavailable | Temporarily unavailable. Retry with backoff. |
The OAuth endpoints answer in the OAuth format instead: { "error": "invalid_grant", "error_description": "…" }.
All paths are relative to https://i-quote.app/api/v1. 43 endpoints; the OpenAPI file has the full request and response schemas.
Who the credential is, what it may do, and which businesses it reaches. Neither endpoint needs a scope or the IQuote-Business header.
Describe this credential
Credential type (OAuth access token or API key), the user it acts as, its scopes and the business ids it is connected to.
List connected businesses
Every business this credential is connected to, with the user's role, the plan, and whether the API can be used on it right now (api_access, and api_access_error when not).
The same rules as the customer form in the app: one customer per email per business, and a customer with quotes, invoices or payments cannot be deleted.
List customers
Newest first.
Query parameters
Create a customer
JSON body (CustomerCreate)
A customer needs a name: send first_name and/or last_name, company_name or name.
Create or update up to 100 customers
Rows are matched by external_id, then email (match_on), updated when matched and created otherwise. One result per row, in order; a bad row does not stop the others.
JSON body (CustomerBulkUpsert)
CustomerUpdate
Retrieve a customer
Update a customer
JSON body (CustomerUpdate)
Only the fields sent change.
Delete a customer
Refused (409 customer_has_history) when the customer has quotes, invoices or payments.
Quotes are created as drafts, numbered and totalled like the app does. Sending emails the customer from the business, in its branding.
List quotes
Newest first.
Query parameters
Create a draft quote
JSON body (QuoteCreate)
Created as a draft with the next quote number. Totals are computed for you.
LineItemInput
DepositInput
Retrieve a quote
Update a quote
Accepted and converted quotes are records and cannot be edited (409 quote_not_editable); duplicate them instead.
JSON body (QuoteUpdate)
Only while the quote is editable (draft, sent, viewed or declined). Editing a declined quote reopens it as a draft.
LineItemInput
DepositInput
Delete a draft quote
Only drafts (409 quote_not_draft).
Email the quote to the customer
Draft, sent and viewed quotes only. Emails first, then marks the quote sent (a resend keeps its status). Needs a customer email. Retry with the same Idempotency-Key to avoid a second email.
Copy into a new draft
Convert to an invoice
Sent, viewed and accepted quotes can be converted. Returns the invoice: 201 when created, 200 when the quote was already converted (idempotent).
Invoices can be edited until money moves on them. Payments recorded by hand, refunds and voids follow the app's money rules.
List invoices
Newest first.
Query parameters
Create a draft invoice
JSON body (InvoiceCreate)
Created as a draft with the next invoice number. Totals and amount_due are computed for you.
InvoiceLineItemInput
DepositInput
Retrieve an invoice
Update an invoice
Only while the invoice is draft, sent, viewed or overdue with nothing paid (409 invoice_not_editable).
JSON body (InvoiceUpdate)
Only until money moves on the invoice. Moving the due date of an overdue invoice to today or later reopens it.
InvoiceLineItemInput
DepositInput
Delete a draft invoice
Only drafts with no payments. Void a sent invoice instead.
Email the invoice to the customer
Sets the issue and due dates the way the app does on first send, emails, then marks the invoice sent. Retry with the same Idempotency-Key to avoid a second email.
Send a payment reminder
A "due soon" reminder before the due date, an overdue notice after it. Only on invoices awaiting payment.
Pause or resume automatic reminders
JSON body (PauseReminders)
Stop a recurring series
Call it on the first invoice of the series. No further invoices are created.
Void an invoice
Only without money on it: refund or remove payments first.
Refund card payments
Refunds card payments through the business's connected Stripe account. Owner or admin only. Requires an Idempotency-Key header (400 idempotency_key_required). At most 20 API refunds per business per hour (429 refund_rate_limited). The business is notified of every API refund. pending is true while Stripe is still processing.
JSON body (RefundCreate)
Refunds card payments through the business's connected Stripe account.
Get the customer payment page
The hosted page where the customer views and pays the invoice, and whether it can take a card payment now (payable, reason). Changes nothing.
List the invoice's payments
All of them, newest first (not paginated).
Record a payment received outside iQuote
Requires an Idempotency-Key header (400 idempotency_key_required), so a retry never records the money twice.
JSON body (PaymentCreate)
Money received outside iQuote (bank transfer, cash, cheque). Card payments arrive on their own.
Remove a recorded payment
Only payments recorded by hand, and only by an owner or admin. Card payments are refunded instead (409 card_payment).
Money received: card payments through Stripe and payments recorded by hand.
List payments
Newest first.
Query parameters
Retrieve a payment
Money per currency, never added across currencies.
Summary for a period
invoiced, collected (net of refunds), refunded, outstanding and overdue per currency, plus counts. With quotes:read as well, quote counts by status and the acceptance rate.
Query parameters
Up to 10 endpoints per business. The signing secret is returned only when an endpoint is created and when its secret is rotated. Through the API an app or key sees and manages only the endpoints it created, may only subscribe to events it can read (customer.* needs customers:read, quote.* quotes:read, invoice.* invoices:read, payment.* payments:read; * needs all four), and its endpoints are disabled when the connection or key is revoked. Endpoints added in Settings belong to the business.
List webhook endpoints
The endpoints this app or key created (not paginated).
Create a webhook endpoint
The response includes the signing secret (whsec_...). Store it: it is not shown again.
JSON body (WebhookEndpointCreate)
Retrieve a webhook endpoint
Update a webhook endpoint
JSON body (WebhookEndpointUpdate)
Delete a webhook endpoint
Rotate the signing secret
Returns the new secret. The old one stops working at once.
Send a test ping
Delivers a signed ping event now (not stored, not retried) and reports what the endpoint answered.
List recent deliveries
Newest first, up to limit (default 50, at most 100). Not paginated.
Query parameters
Retry a delivery now
Queues a finished or failed delivery to be sent again within a minute.
iQuote POSTs an event to your endpoint when something happens in a business. Create endpoints with the webhooks:manage scope (or in Settings → Developers), up to 10 per business.
curl https://i-quote.app/api/v1/webhook_endpoints \
-H "Authorization: Bearer $IQUOTE_TOKEN" \
-H "IQuote-Business: $BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.example/webhooks/iquote",
"events": ["invoice.paid", "invoice.overdue", "payment.received"],
"description": "Accounting sync"
}'
# The answer carries "secret": "whsec_..." once. Store it.| Event | data.object | When |
|---|---|---|
quote.sent | Quote | A quote was sent to the customer for the first time (its status became sent). |
quote.viewed | Quote | The customer opened the quote link. |
quote.accepted | Quote | The customer accepted the quote. |
quote.declined | Quote | The customer declined the quote. |
quote.converted | Quote | The quote became an invoice (converted_invoice_id is set). |
invoice.sent | Invoice | An invoice was sent for the first time (its status became sent). |
invoice.viewed | Invoice | The customer opened the invoice link. |
invoice.paid | Invoice | The invoice is paid in full. |
invoice.partially_paid | Invoice | A payment left part of the invoice still to pay (a deposit, or a part payment). |
invoice.overdue | Invoice | The due date passed with money still owed. |
invoice.voided | Invoice | The invoice was voided. |
payment.received | Payment | A payment succeeded: a card payment, or one recorded by hand. |
payment.refunded | Payment | Money was refunded on a payment. The object also carries refunded_now, the amount of this refund. |
customer.created | Customer | A customer was added, in the app, by import or through the API. |
Subscribe to ["*"] for every event. data.object has the same shape the API returns for that object. Events are recorded only while an enabled endpoint is subscribed to them.
POST /webhooks/iquote HTTP/1.1
Content-Type: application/json
User-Agent: iQuote-Webhooks/1.0 (+https://i-quote.app/developers)
IQuote-Signature: t=1792152000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
IQuote-Event-Id: 0b8f3c1e-2a4d-4e6f-9a1b-3c5d7e9f1a2b
IQuote-Event-Type: invoice.paid
IQuote-Delivery-Id: 7d2e4f6a-8b0c-4d1e-a2f3-b4c5d6e7f8a9
IQuote-Business: 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
{
"id": "0b8f3c1e-2a4d-4e6f-9a1b-3c5d7e9f1a2b",
"object": "event",
"type": "invoice.paid",
"created_at": "2026-10-16T09:20:00.000Z",
"business_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"data": {
"object": {
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"object": "invoice",
"number": "INV-0042",
"status": "paid",
"currency": "USD",
"total": 2430,
"amount_paid": 2430,
"amount_due": 0
}
}
}| Header | Example | Meaning |
|---|---|---|
IQuote-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> | Signature over "<t>.<raw body>" with the endpoint signing secret. |
IQuote-Event-Id | uuid | The event id, the same as the body id. Deduplicate on it. |
IQuote-Event-Type | invoice.paid | The event type, the same as the body type. |
IQuote-Delivery-Id | uuid | This delivery. Retries of one delivery keep the id. |
IQuote-Business | uuid | The business the event belongs to. |
User-Agent | iQuote-Webhooks/1.0 | Identifies iQuote deliveries. |
IQuote-Signature is t=<unix seconds>,v1=<signature>. The signature is the hex HMAC-SHA256 of the string "<t>.<raw body>", keyed with the endpoint’s signing secret: the whole whsec_… string. Compute it over the raw bytes you received, before parsing JSON, compare in constant time, and reject timestamps more than 5 minutes from your clock.
import crypto from 'node:crypto';
import express from 'express';
const { IQUOTE_WEBHOOK_SECRET } = process.env; // whsec_..., the whole string is the key
function expectedSignature(secret, timestamp, rawBody) {
return crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
}
function verifyIQuoteSignature(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
const parts = {};
for (const piece of String(signatureHeader || '').split(',')) {
const i = piece.indexOf('=');
if (i > 0) parts[piece.slice(0, i).trim()] = piece.slice(i + 1).trim();
}
const timestamp = Number(parts.t);
if (!Number.isInteger(timestamp) || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false; // replayed or stale
const expected = Buffer.from(expectedSignature(secret, timestamp, rawBody), 'hex');
const received = Buffer.from(parts.v1, 'hex');
return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}
const app = express();
// The signature covers the exact bytes sent: read the raw body, not parsed JSON.
app.post('/webhooks/iquote', express.raw({ type: 'application/json' }), async (req, res) => {
const rawBody = req.body.toString('utf8');
if (!verifyIQuoteSignature(rawBody, req.get('IQuote-Signature'), IQUOTE_WEBHOOK_SECRET)) {
return res.status(400).send('invalid signature');
}
const event = JSON.parse(rawBody);
res.sendStatus(200); // answer within 10 seconds; do slow work after
// Deliveries can repeat (retries, a manual retry): handle each event id once.
if (await alreadyProcessed(event.id)) return;
await handleEvent(event); // event.type, event.business_id, event.data.object
});Answer with any 2xx within 10 seconds; do slow work after answering. Anything else, a redirect (redirects are not followed) or no answer in time is a failure.
A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours, 24 hours: 8 attempts in all, then it is marked failed. You can retry any delivery from the API (POST /v1/webhook_deliveries/{id}/retry) or Settings. An endpoint that fails 50 times in a row and has not succeeded for 3 days is disabled; re-enable it with PATCH enabled: true.
The same event can arrive more than once and events can arrive out of order. Deduplicate on the event id, and read created_at or fetch the object when order matters.
Endpoints must be https on a public host (port 443 or 8443). iQuote resolves the host itself and never delivers to private, loopback or link-local addresses. Rotating the secret replaces it at once; POST …/test sends a signed ping event to check your setup.
Email us with the request id of anything unexpected and a person replies. support@i-quote.app