Integrations API
Every Integrations action (22) and data model (8): endpoints, scopes, input, preconditions, events and examples.
Marketplaces, online shops, carriers, e-invoice and Google reviews, synced into your own orders, products, stock and customers.
Module code integrations · 22 actions · 8 data models · 22 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: integrations (everything), integrations:read (read only) or integrations:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
carrier_shipments | carrier_shipment | /api/v1/carrier_shipments | |||
channel_listings | channel_listing | /api/v1/channel_listings | |||
einvoice_documents | einvoice_document | /api/v1/einvoice_documents | |||
integration_accounts | integration_account | /api/v1/integration_accounts | |||
integration_inbounds | integration_inbound | /api/v1/integration_inbounds | |||
integration_issues | integration_issue | /api/v1/integration_issues | |||
integration_reviews | integration_review | /api/v1/integration_reviews | |||
integration_runs | integration_run | /api/v1/integration_runs |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
integrations.cancel_shipment | integrations:write | Cancel a parcel |
integrations.connect | integrations:manage | Connect an app for syncing |
integrations.create_shipment | integrations:write | Send with a carrier |
integrations.disconnect | integrations:delete | Disconnect an app |
integrations.issue_einvoice | integrations:write | Invoice an order (e-Arşiv / e-Fatura) |
integrations.map_listing | integrations:write | Tie a listing to a product |
integrations.publish_listings | integrations:write | Publish product variants to a marketplace |
integrations.raise_issue | integrations:handle | Raise a sync issue |
integrations.record_einvoice | integrations:handle | Record an e-invoice answer |
integrations.record_inbound | integrations:handle | Record a webhook delivery |
integrations.record_review_reply | integrations:handle | Record a review reply answer |
integrations.record_reviews | integrations:handle | Record reviews |
integrations.record_run | integrations:handle | Record a sync run |
integrations.record_shipment | integrations:handle | Record a carrier answer |
integrations.record_stock_push | integrations:handle | Record a stock push |
integrations.record_test | integrations:manage | Record a connection test |
integrations.reply_review | integrations:write | Reply to a review |
integrations.request_sync | integrations:write | Sync now |
integrations.resolve_issue | integrations:write | Close a sync issue |
integrations.set_paused | integrations:manage | Pause or resume syncing |
integrations.update_settings | integrations:manage | Change what an app syncs |
integrations.upsert_listings | integrations:handle | Record listings |
integrations.cancel_shipment
Cancel a parcel — Cancels a parcel the carrier has not picked up yet; the carrier is told in the background.
Needs integrations:write · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/carrier_shipments/{id}/actions/cancel_shipment- Canonical address:
POST /api/v1/actions/integrations.cancel_shipment
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Parcel · id of carrier_shipment |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The parcel must exist. |
not_cancellable | Only before pick-up. |
Events it publishes: integrations.shipment_updated
What it writes: carrier_shipment (update) — Marks it cancelled
curl -X POST https://app.getera.io/api/v1/carrier_shipments/CARRIER_SHIPMENT_ID/actions/cancel_shipment \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"integrations.shipment_updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.carrierShipments.cancelShipment("CARRIER_SHIPMENT_ID")era carrier_shipments cancel_shipment CARRIER_SHIPMENT_ID{
"name": "era__integrations__cancel_shipment",
"arguments": {
"id": "CARRIER_SHIPMENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_cancellable · 429 slow down
integrations.connect
Connect an app for syncing — Links a connection (its sealed key) to what Era syncs it with: a sales channel and warehouse for a marketplace or shop.
Needs integrations:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/integration_accounts/actions/connect- Canonical address:
POST /api/v1/actions/integrations.connect
Input
| Field | Type | Required | Notes |
|---|---|---|---|
provider | string | yes | App · max 41 |
label | string | Name · max 160 | |
credentialId | uuid | Connection · id of credential | |
channelId | uuid | Sales channel · id of channel | |
warehouseId | uuid | Warehouse · id of warehouse | |
config | object / array | Account settings | |
settings | object / array | What to sync |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
unknown_provider | The app must be one Era integrates. |
credential_not_found | The connection must exist in this workspace. |
provider_mismatch | The connection must be for the same app. |
credential_taken | One connection feeds one integration account. |
channel_required | A marketplace or shop needs an existing sales channel. |
channel_taken | One sales channel is fed by one account. |
bad_config | The warehouse must exist and the settings stay small. |
bad_setting | Only known sync settings, with sensible values. |
Events it publishes: integrations.connected
What it writes: integration_account (insert) — The account, with a fresh inbound-webhook token
curl -X POST https://app.getera.io/api/v1/integration_accounts/actions/connect \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"provider":"App"}'{
"ok": true,
"result": "…",
"events": [
"integrations.connected"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationAccounts.connect({
provider: "App"
})era integration_accounts connect \
--provider "App"{
"name": "era__integrations__connect",
"arguments": {
"provider": "App"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_provider, credential_not_found, provider_mismatch, credential_taken, channel_required, channel_taken, bad_config, bad_setting · 429 slow down
integrations.create_shipment
Send with a carrier — Books a parcel for an order with a connected carrier (Yurtiçi, Aras, MNG, Sürat, HepsiJet); tracking follows by itself.
Needs integrations:write · changes data
https://app.getera.io/api/v1/carrier_shipments/actions/create_shipment- Canonical address:
POST /api/v1/actions/integrations.create_shipment
Input
| Field | Type | Required | Notes |
|---|---|---|---|
orderId | uuid | yes | Order · id of sales_order |
accountId | uuid | Carrier account · Empty: the first connected carrier. · id of integration_account | |
pieces | number | Parcels · min 1 · max 99 | |
desi | number | Desi · min 0 · max 1000 | |
weightKg | number | Weight (kg) · min 0 · max 1000 | |
codAmount | number (money) | Cash on delivery (minor units) · min 0 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
order_not_found | The order must exist. |
no_shipping_account | A carrier must be connected and active. |
not_shipping_account | The named account must be a carrier. |
order_not_shippable | A draft, cancelled, expired or returned order does not ship. |
shipment_exists | One open parcel per order and carrier. |
no_address | The order needs a delivery address with a street and a city. |
Events it publishes: integrations.shipment_requested
What it writes: carrier_shipment (insert) — The parcel request; the worker books it at the carrier
curl -X POST https://app.getera.io/api/v1/carrier_shipments/actions/create_shipment \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"orderId":"SALES_ORDER_ID"}'{
"ok": true,
"result": "…",
"events": [
"integrations.shipment_requested"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.carrierShipments.createShipment({
orderId: "SALES_ORDER_ID"
})era carrier_shipments create_shipment \
--order-id "SALES_ORDER_ID"{
"name": "era__integrations__create_shipment",
"arguments": {
"orderId": "SALES_ORDER_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 order_not_found, no_shipping_account, not_shipping_account, order_not_shippable, shipment_exists, no_address · 429 slow down
integrations.disconnect
Disconnect an app — Stops syncing for good. Orders, products and history stay; the key is removed with the connection itself (apps.disconnect).
Needs integrations:delete · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/integration_accounts/{id}/actions/disconnect- Canonical address:
POST /api/v1/actions/integrations.disconnect
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Account · id of integration_account |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
already_disconnected | It is disconnected once. |
Events it publishes: integrations.disconnected
What it writes: integration_account (update) — Marks it disconnected; drops the webhook token
curl -X POST https://app.getera.io/api/v1/integration_accounts/INTEGRATION_ACCOUNT_ID/actions/disconnect \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"integrations.disconnected"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationAccounts.disconnect("INTEGRATION_ACCOUNT_ID")era integration_accounts disconnect INTEGRATION_ACCOUNT_ID{
"name": "era__integrations__disconnect",
"arguments": {
"id": "INTEGRATION_ACCOUNT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_disconnected · 429 slow down
integrations.issue_einvoice
Invoice an order (e-Arşiv / e-Fatura) — Asks the connected e-invoice provider (Paraşüt, Uyumsoft or Logo eLogo) to invoice an order: e-Fatura for a registered taxpayer, e-Arşiv otherwise.
Needs integrations:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/einvoice_documents/actions/issue_einvoice- Canonical address:
POST /api/v1/actions/integrations.issue_einvoice
Input
| Field | Type | Required | Notes |
|---|---|---|---|
orderId | uuid | yes | Order · id of sales_order |
accountId | uuid | E-invoice account · id of integration_account |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
order_not_found | The order must exist. |
no_einvoice_account | An e-invoice provider must be connected and active. |
not_einvoice_account | The named account must be an e-invoice provider. |
order_not_invoiceable | A draft, cancelled or expired order is not invoiced. |
already_invoiced | One live invoice per order and provider. |
Events it publishes: integrations.einvoice_requested
What it writes: einvoice_document (insert) — The request; the worker sends it to the provider
curl -X POST https://app.getera.io/api/v1/einvoice_documents/actions/issue_einvoice \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"orderId":"SALES_ORDER_ID"}'{
"ok": true,
"result": "…",
"events": [
"integrations.einvoice_requested"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.einvoiceDocuments.issueEinvoice({
orderId: "SALES_ORDER_ID"
})era einvoice_documents issue_einvoice \
--order-id "SALES_ORDER_ID"{
"name": "era__integrations__issue_einvoice",
"arguments": {
"orderId": "SALES_ORDER_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 order_not_found, no_einvoice_account, not_einvoice_account, order_not_invoiceable, already_invoiced · 429 slow down
integrations.map_listing
Tie a listing to a product — Says which Era variant a marketplace listing is, so its orders take stock and Era’s stock reaches it.
Needs integrations:write · changes data
https://app.getera.io/api/v1/channel_listings/{id}/actions/map_listing- Canonical address:
POST /api/v1/actions/integrations.map_listing
Input
| Field | Type | Required | Notes |
|---|---|---|---|
listingId | uuid | yes | Filled from the path {id} · Listing · id of channel_listing |
itemVariantId | uuid | Variant · Empty unties it. · id of item_variant | |
source | string (enum) | How · Allowed values: manual, created |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The listing must exist. |
variant_not_found | The variant must exist. |
Events it publishes: integrations.listing_mapped
What it writes: channel_listing (update) — Sets (or clears) the variant; the next push sends its stock
curl -X POST https://app.getera.io/api/v1/channel_listings/CHANNEL_LISTING_ID/actions/map_listing \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"itemVariantId":"ITEM_VARIANT_ID"}'{
"ok": true,
"result": "…",
"events": [
"integrations.listing_mapped"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.channelListings.mapListing("CHANNEL_LISTING_ID", {
itemVariantId: "ITEM_VARIANT_ID"
})era channel_listings map_listing CHANNEL_LISTING_ID \
--item-variant-id "ITEM_VARIANT_ID"{
"name": "era__integrations__map_listing",
"arguments": {
"listingId": "CHANNEL_LISTING_ID",
"itemVariantId": "ITEM_VARIANT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, variant_not_found · 429 slow down
integrations.publish_listings
Publish product variants to a marketplace — After catalog.set_options and catalog.save_variant, publish the variants to Trendyol. Give each catalogue variant a SKU with catalog.save_variant, then supply variantId, barcode, images and Trendyol category/brand/attribute ids. The SKU keeps the approved listing mapped to the same Era variant. Prices come from the catalogue in minor units. Asynchronous: queued does not mean published; read integrations_status for the batch result.
Needs integrations:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/integration_runs/actions/publish_listings- Canonical address:
POST /api/v1/actions/integrations.publish_listings
Input
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | uuid | yes | Account · id of integration_account |
mode | string (enum) | Operation · Allowed values: create, update_unapproved, update_content, update_variant | |
listings | object / array | yes | Variants and marketplace fields · Array: variantId, barcode, quantity (defaults to free channel stock, never more than free stock), images (https URLs), meta: {brandId,categoryId,productMainId,vatRate,attributes}. All variants of one product share productMainId. listPrice is minor units. For approved content updates provide meta.contentId. |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
listing_account_unavailable | An active account supporting listing publication is required. |
invalid_listing_request | 1–1000 unique variants with valid listing data must be visible in the catalogue. |
Events it publishes: integrations.listing_publish_requested
What it writes: integration_run (insert) — Track the asynchronous publication request
curl -X POST https://app.getera.io/api/v1/integration_runs/actions/publish_listings \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"accountId":"INTEGRATION_ACCOUNT_ID","listings":{}}'{
"ok": true,
"result": "…",
"events": [
"integrations.listing_publish_requested"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationRuns.publishListings({
accountId: "INTEGRATION_ACCOUNT_ID",
listings: {}
})era integration_runs publish_listings --data '{"accountId":"INTEGRATION_ACCOUNT_ID","listings":{}}'{
"name": "era__integrations__publish_listings",
"arguments": {
"accountId": "INTEGRATION_ACCOUNT_ID",
"listings": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 listing_account_unavailable, invalid_listing_request · 429 slow down
integrations.raise_issue
Raise a sync issue — Something a person has to look at, in Turkish. The same thing again counts up instead of repeating.
Needs integrations:handle · changes data
https://app.getera.io/api/v1/integration_issues/actions/raise_issue- Canonical address:
POST /api/v1/actions/integrations.raise_issue
Input
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | uuid | yes | Account · id of integration_account |
kind | string | yes | Kind · max 40 |
externalRef | string | About · max 300 | |
message | string | yes | Message · max 2000 |
detail | object / array | Detail | |
orderId | uuid | Order · id of sales_order | |
itemVariantId | uuid | Variant · id of item_variant |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
bad_kind | A known issue kind. |
Events it publishes: integrations.issue_raised
What it writes: integration_issue (upsert) — One open issue per (account, kind, thing)
curl -X POST https://app.getera.io/api/v1/integration_issues/actions/raise_issue \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"accountId":"INTEGRATION_ACCOUNT_ID","kind":"Kind","message":"Message"}'{
"ok": true,
"result": "…",
"events": [
"integrations.issue_raised"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationIssues.raiseIssue({
accountId: "INTEGRATION_ACCOUNT_ID",
kind: "Kind",
message: "Message"
})era integration_issues raise_issue \
--account-id "INTEGRATION_ACCOUNT_ID" \
--kind "Kind" \
--message "Message"{
"name": "era__integrations__raise_issue",
"arguments": {
"accountId": "INTEGRATION_ACCOUNT_ID",
"kind": "Kind",
"message": "Message"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, bad_kind · 429 slow down
integrations.record_einvoice
Record an e-invoice answer — What the e-invoice provider did with the request.
Needs integrations:handle · changes data
https://app.getera.io/api/v1/einvoice_documents/{id}/actions/record_einvoice- Canonical address:
POST /api/v1/actions/integrations.record_einvoice
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · E-invoice · id of einvoice_document |
status | string | yes | Status · max 20 |
kind | string | Kind · max 20 | |
externalId | string | Provider id · max 200 | |
documentNumber | string | Number · max 100 | |
pdfUrl | string | PDF · max 1000 | |
error | string | Refusal · max 1000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The e-invoice must exist. |
Events it publishes: integrations.einvoice_updated
What it writes: einvoice_document (update) — The provider’s answer
curl -X POST https://app.getera.io/api/v1/einvoice_documents/EINVOICE_DOCUMENT_ID/actions/record_einvoice \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"status":"Status"}'{
"ok": true,
"result": "…",
"events": [
"integrations.einvoice_updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.einvoiceDocuments.recordEinvoice("EINVOICE_DOCUMENT_ID", {
status: "Status"
})era einvoice_documents record_einvoice EINVOICE_DOCUMENT_ID \
--status "Status"{
"name": "era__integrations__record_einvoice",
"arguments": {
"id": "EINVOICE_DOCUMENT_ID",
"status": "Status"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
integrations.record_inbound
Record a webhook delivery — Each delivery once; an accepted one nudges a sync (its body is never trusted as data).
Needs integrations:handle · changes data
https://app.getera.io/api/v1/integration_inbounds/actions/record_inbound- Canonical address:
POST /api/v1/actions/integrations.record_inbound
Input
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | uuid | yes | Account · id of integration_account |
externalId | string | yes | Delivery · max 300 |
topic | string | Topic · max 200 | |
accepted | boolean | yes | Accepted |
reason | string | Why refused · max 300 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
Events it publishes: integrations.webhook_received
What it writes: integration_inbound (insert) — The delivery, deduped on its id
curl -X POST https://app.getera.io/api/v1/integration_inbounds/actions/record_inbound \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"accountId":"INTEGRATION_ACCOUNT_ID","externalId":"Delivery","accepted":true}'{
"ok": true,
"result": "…",
"events": [
"integrations.webhook_received"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationInbounds.recordInbound({
accountId: "INTEGRATION_ACCOUNT_ID",
externalId: "Delivery",
accepted: true
})era integration_inbounds record_inbound \
--account-id "INTEGRATION_ACCOUNT_ID" \
--external-id "Delivery" \
--accepted true{
"name": "era__integrations__record_inbound",
"arguments": {
"accountId": "INTEGRATION_ACCOUNT_ID",
"externalId": "Delivery",
"accepted": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
integrations.record_review_reply
Record a review reply answer — Whether the review provider published the reply.
Needs integrations:handle · changes data
https://app.getera.io/api/v1/integration_reviews/{id}/actions/record_review_reply- Canonical address:
POST /api/v1/actions/integrations.record_review_reply
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Review · id of integration_review |
ok | boolean | yes | Published |
repliedAt | string | When · max 40 | |
error | string | Refusal · max 1000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The review must exist. |
Events it publishes: integrations.review_replied
What it writes: integration_review (update) — The provider’s answer
curl -X POST https://app.getera.io/api/v1/integration_reviews/INTEGRATION_REVIEW_ID/actions/record_review_reply \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"ok":true}'{
"ok": true,
"result": "…",
"events": [
"integrations.review_replied"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationReviews.recordReviewReply("INTEGRATION_REVIEW_ID", {
ok: true
})era integration_reviews record_review_reply INTEGRATION_REVIEW_ID \
--ok true{
"name": "era__integrations__record_review_reply",
"arguments": {
"id": "INTEGRATION_REVIEW_ID",
"ok": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
integrations.record_reviews
Record reviews — The reviews a connected review provider holds now: new ones, edited ones, and replies made outside Era.
Needs integrations:handle · changes data
https://app.getera.io/api/v1/integration_reviews/actions/record_reviews- Canonical address:
POST /api/v1/actions/integrations.record_reviews
Input
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | uuid | yes | Account · id of integration_account |
reviews | object / array | yes | Reviews |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
not_reviews_account | The account must be a review provider (Google Business Profile). |
invalid_reviews | The reviews must be a list. |
Events it publishes: integrations.reviews_synced
What it writes: integration_review (insert) — One row per review, updated in place on the next sync
curl -X POST https://app.getera.io/api/v1/integration_reviews/actions/record_reviews \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"accountId":"INTEGRATION_ACCOUNT_ID","reviews":{}}'{
"ok": true,
"result": "…",
"events": [
"integrations.reviews_synced"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationReviews.recordReviews({
accountId: "INTEGRATION_ACCOUNT_ID",
reviews: {}
})era integration_reviews record_reviews --data '{"accountId":"INTEGRATION_ACCOUNT_ID","reviews":{}}'{
"name": "era__integrations__record_reviews",
"arguments": {
"accountId": "INTEGRATION_ACCOUNT_ID",
"reviews": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_reviews_account, invalid_reviews · 429 slow down
integrations.record_run
Record a sync run — Opens a run, or closes it with its counts and one Turkish sentence; moves the account’s cursors and health.
Needs integrations:handle · changes data
https://app.getera.io/api/v1/integration_accounts/{id}/actions/record_run- Canonical address:
POST /api/v1/actions/integrations.record_run
Input
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | uuid | yes | Filled from the path {id} · Account · id of integration_account |
runId | uuid | Run | |
feed | string | Feed · max 20 | |
trigger | string | Trigger · max 20 | |
status | string (enum) | Status · Allowed values: running, ok, partial, failed, skipped | |
counts | object / array | Counts | |
message | string | Message · max 1000 | |
jobId | uuid | Job | |
cursors | object / array | Cursors | |
touch | string | Feed done · max 20 | |
error | string | Error · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account (and the run, when named) must exist. |
Events it publishes: integrations.run_finished
What it writes: integration_run (upsert) — The run row; integration_account (update) — Last sync times, cursors, health
curl -X POST https://app.getera.io/api/v1/integration_accounts/INTEGRATION_ACCOUNT_ID/actions/record_run \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"runId":"RUN_ID"}'{
"ok": true,
"result": "…",
"events": [
"integrations.run_finished"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationAccounts.recordRun("INTEGRATION_ACCOUNT_ID", {
runId: "RUN_ID"
})era integration_accounts record_run INTEGRATION_ACCOUNT_ID \
--run-id "RUN_ID"{
"name": "era__integrations__record_run",
"arguments": {
"accountId": "INTEGRATION_ACCOUNT_ID",
"runId": "RUN_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
integrations.record_shipment
Record a carrier answer — The carrier’s booking, tracking number, label or latest movement — or its refusal.
Needs integrations:handle · changes data
https://app.getera.io/api/v1/carrier_shipments/{id}/actions/record_shipment- Canonical address:
POST /api/v1/actions/integrations.record_shipment
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Parcel · id of carrier_shipment |
status | string | Status · max 20 | |
trackingNumber | string | Tracking number · max 100 | |
barcode | string | Barcode · max 100 | |
labelUrl | string | Label · max 1000 | |
trackingUrl | string | Tracking page · max 1000 | |
lastEvent | string | Latest movement · max 500 | |
lastEventAt | string | When · max 40 | |
error | string | Refusal · max 1000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The parcel must exist. |
Events it publishes: integrations.shipment_updated
What it writes: carrier_shipment (update) — What the carrier said
curl -X POST https://app.getera.io/api/v1/carrier_shipments/CARRIER_SHIPMENT_ID/actions/record_shipment \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"status":"Status"}'{
"ok": true,
"result": "…",
"events": [
"integrations.shipment_updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.carrierShipments.recordShipment("CARRIER_SHIPMENT_ID", {
status: "Status"
})era carrier_shipments record_shipment CARRIER_SHIPMENT_ID \
--status "Status"{
"name": "era__integrations__record_shipment",
"arguments": {
"id": "CARRIER_SHIPMENT_ID",
"status": "Status"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
integrations.record_stock_push
Record a stock push — What each listing was told and whether the channel took it.
Needs integrations:handle · changes data
https://app.getera.io/api/v1/integration_accounts/{id}/actions/record_stock_push- Canonical address:
POST /api/v1/actions/integrations.record_stock_push
Input
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | uuid | yes | Filled from the path {id} · Account · id of integration_account |
results | object / array | yes | Results |
batchRef | string | Provider batch · max 200 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
malformed_results | Push results are a list. |
Events it publishes: integrations.stock_pushed
What it writes: channel_listing (update) — Last pushed quantity, or the refusal; integration_account (update) — Last push time
curl -X POST https://app.getera.io/api/v1/integration_accounts/INTEGRATION_ACCOUNT_ID/actions/record_stock_push \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"results":{}}'{
"ok": true,
"result": "…",
"events": [
"integrations.stock_pushed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationAccounts.recordStockPush("INTEGRATION_ACCOUNT_ID", {
results: {}
})era integration_accounts record_stock_push INTEGRATION_ACCOUNT_ID --data '{"results":{}}'{
"name": "era__integrations__record_stock_push",
"arguments": {
"accountId": "INTEGRATION_ACCOUNT_ID",
"results": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, malformed_results · 429 slow down
integrations.record_test
Record a connection test — Keeps the result of one harmless call to the provider on the account.
Needs integrations:manage · changes data
https://app.getera.io/api/v1/integration_accounts/{id}/actions/record_test- Canonical address:
POST /api/v1/actions/integrations.record_test
Input
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | uuid | yes | Filled from the path {id} · Account · id of integration_account |
ok | boolean | yes | Passed |
message | string | yes | What happened · max 1000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
Events it publishes: integrations.tested
What it writes: integration_account (update) — Last test time and result; integration_run (insert) — The test in the run history
curl -X POST https://app.getera.io/api/v1/integration_accounts/INTEGRATION_ACCOUNT_ID/actions/record_test \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"ok":true,"message":"What happened"}'{
"ok": true,
"result": "…",
"events": [
"integrations.tested"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationAccounts.recordTest("INTEGRATION_ACCOUNT_ID", {
ok: true,
message: "What happened"
})era integration_accounts record_test INTEGRATION_ACCOUNT_ID \
--ok true \
--message "What happened"{
"name": "era__integrations__record_test",
"arguments": {
"accountId": "INTEGRATION_ACCOUNT_ID",
"ok": true,
"message": "What happened"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
integrations.reply_review
Reply to a review — Posts the business’s public reply to a Google review (replaces an earlier reply); sent in the background.
Needs integrations:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/integration_reviews/{id}/actions/reply_review- Canonical address:
POST /api/v1/actions/integrations.reply_review
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Review · id of integration_review |
text | string | yes | Reply · At most 4,000 characters (Google’s limit). |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The review must exist. |
reply_empty | The reply must say something. |
reply_too_long | At most 4,000 characters. |
account_inactive | The review’s account must be connected. |
reply_in_flight | One reply at a time per review. |
Events it publishes: integrations.review_reply_requested
What it writes: integration_review (update) — Keeps the reply and marks it on its way
curl -X POST https://app.getera.io/api/v1/integration_reviews/INTEGRATION_REVIEW_ID/actions/reply_review \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"text":"Reply"}'{
"ok": true,
"result": "…",
"events": [
"integrations.review_reply_requested"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationReviews.replyReview("INTEGRATION_REVIEW_ID", {
text: "Reply"
})era integration_reviews reply_review INTEGRATION_REVIEW_ID \
--text "Reply"{
"name": "era__integrations__reply_review",
"arguments": {
"id": "INTEGRATION_REVIEW_ID",
"text": "Reply"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, reply_empty, reply_too_long, account_inactive, reply_in_flight · 429 slow down
integrations.request_sync
Sync now — Fetches new orders and products from a marketplace or shop and sends Era’s stock back — in the background, in seconds.
Needs integrations:write · changes data
https://app.getera.io/api/v1/integration_runs/actions/request_sync- Canonical address:
POST /api/v1/actions/integrations.request_sync
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | Account · id of integration_account | |
provider | string | App · Instead of the id: trendyol, hepsiburada, amazon, shopify, woocommerce, n11, ciceksepeti, ikas, ticimax, ideasoft, etsy. · max 41 | |
feed | string (enum) | What · orders = fetch new orders, stock = send Era’s stock, products = re-read listings, all = everything. · Allowed values: orders, products, stock, reviews, all | |
trigger | string (enum) | Asked by · Allowed values: manual, era, schedule, webhook, connect, event |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist (by id, or the one account of the named app). |
ambiguous_account | Named by app, there must be exactly one account of it. |
not_syncable | Only marketplaces, shops and review providers sync on their own. |
disconnected | A disconnected account does not sync. |
paused | A paused account does not sync. |
sync_running | One run of a feed at a time. |
Events it publishes: integrations.sync_requested
What it writes: integration_run (insert) — The run, which the worker picks up
curl -X POST https://app.getera.io/api/v1/integration_runs/actions/request_sync \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"INTEGRATION_ACCOUNT_ID"}'{
"ok": true,
"result": "…",
"events": [
"integrations.sync_requested"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationRuns.requestSync({
id: "INTEGRATION_ACCOUNT_ID"
})era integration_runs request_sync \
--id "INTEGRATION_ACCOUNT_ID"{
"name": "era__integrations__request_sync",
"arguments": {
"id": "INTEGRATION_ACCOUNT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, ambiguous_account, not_syncable, disconnected, paused, sync_running · 429 slow down
integrations.resolve_issue
Close a sync issue — Marks it resolved (fixed) or ignored (not a problem).
Needs integrations:write · changes data
https://app.getera.io/api/v1/integration_issues/{id}/actions/resolve_issue- Canonical address:
POST /api/v1/actions/integrations.resolve_issue
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Issue · id of integration_issue |
status | string (enum) | Close as · Allowed values: resolved, ignored |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The issue must exist. |
already_closed | An issue is closed once. |
Events it publishes: integrations.issue_resolved
What it writes: integration_issue (update) — Closes it
curl -X POST https://app.getera.io/api/v1/integration_issues/INTEGRATION_ISSUE_ID/actions/resolve_issue \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"status":"resolved"}'{
"ok": true,
"result": "…",
"events": [
"integrations.issue_resolved"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationIssues.resolveIssue("INTEGRATION_ISSUE_ID", {
status: "resolved"
})era integration_issues resolve_issue INTEGRATION_ISSUE_ID \
--status "resolved"{
"name": "era__integrations__resolve_issue",
"arguments": {
"id": "INTEGRATION_ISSUE_ID",
"status": "resolved"
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_closed · 429 slow down
integrations.set_paused
Pause or resume syncing — A paused account keeps everything; nothing is fetched or pushed until it is resumed.
Needs integrations:manage · changes data
https://app.getera.io/api/v1/integration_accounts/{id}/actions/set_paused- Canonical address:
POST /api/v1/actions/integrations.set_paused
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Account · id of integration_account |
paused | boolean | yes | Paused |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
disconnected | A disconnected account is connected again, not resumed. |
Events it publishes: integrations.paused, integrations.resumed
What it writes: integration_account (update) — Flips paused / active
curl -X POST https://app.getera.io/api/v1/integration_accounts/INTEGRATION_ACCOUNT_ID/actions/set_paused \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"paused":true}'{
"ok": true,
"result": "…",
"events": [
"integrations.paused",
"integrations.resumed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationAccounts.setPaused("INTEGRATION_ACCOUNT_ID", {
paused: true
})era integration_accounts set_paused INTEGRATION_ACCOUNT_ID \
--paused true{
"name": "era__integrations__set_paused",
"arguments": {
"id": "INTEGRATION_ACCOUNT_ID",
"paused": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, disconnected · 429 slow down
integrations.update_settings
Change what an app syncs — Orders in, products in, stock out, the stock buffer, how far back to look.
Needs integrations:manage · changes data
https://app.getera.io/api/v1/integration_accounts/{id}/actions/update_settings- Canonical address:
POST /api/v1/actions/integrations.update_settings
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Account · id of integration_account |
settings | object / array | What to sync | |
config | object / array | Account settings | |
label | string | Name · max 160 | |
warehouseId | uuid | Warehouse · id of warehouse |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
disconnected | A disconnected account is connected again, not edited. |
bad_setting | Only known sync settings, with sensible values. |
Events it publishes: integrations.settings_changed
What it writes: integration_account (update) — Merges the settings
curl -X POST https://app.getera.io/api/v1/integration_accounts/INTEGRATION_ACCOUNT_ID/actions/update_settings \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"settings":{}}'{
"ok": true,
"result": "…",
"events": [
"integrations.settings_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.integrationAccounts.updateSettings("INTEGRATION_ACCOUNT_ID", {
settings: {}
})era integration_accounts update_settings INTEGRATION_ACCOUNT_ID --data '{"settings":{}}'{
"name": "era__integrations__update_settings",
"arguments": {
"id": "INTEGRATION_ACCOUNT_ID",
"settings": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, disconnected, bad_setting · 429 slow down
integrations.upsert_listings
Record listings — Stores what a channel sells and ties each listing to an Era variant by SKU, barcode or external key.
Needs integrations:handle · changes data
https://app.getera.io/api/v1/channel_listings/actions/upsert_listings- Canonical address:
POST /api/v1/actions/integrations.upsert_listings
Input
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | uuid | yes | Account · id of integration_account |
listings | object / array | yes | Listings |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The account must exist. |
malformed_listings | A list of at most 1000 listings, each with a key. |
Events it publishes: integrations.listings_synced
What it writes: channel_listing (upsert) — One row per listing; a match is never undone by a re-read
curl -X POST https://app.getera.io/api/v1/channel_listings/actions/upsert_listings \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"accountId":"INTEGRATION_ACCOUNT_ID","listings":{}}'{
"ok": true,
"result": "…",
"events": [
"integrations.listings_synced"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.channelListings.upsertListings({
accountId: "INTEGRATION_ACCOUNT_ID",
listings: {}
})era channel_listings upsert_listings --data '{"accountId":"INTEGRATION_ACCOUNT_ID","listings":{}}'{
"name": "era__integrations__upsert_listings",
"arguments": {
"accountId": "INTEGRATION_ACCOUNT_ID",
"listings": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold integrations:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, malformed_listings · 429 slow down
Data models
CarrierShipment
Table carrier_shipment · Resource carrier_shipments
GET /api/v1/carrier_shipmentsGET /api/v1/carrier_shipments/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
account_id | string (uuid) · uuid | integration_account | ||
order_id | string (uuid) · uuid | yes | sales_order | |
reference | string · text | |||
carrier | string · text | |||
tracking_number | string · text | yes | ||
barcode | string · text | yes | ||
label_url | string · text | yes | ||
tracking_url | string · text | yes | ||
status | string · text | requested, created, in_transit, delivered, returned, cancelled, failed | ||
last_event | string · text | yes | ||
last_event_at | string (date-time) · timestamptz | yes | ||
receiver | object · jsonb | |||
pieces | integer · int4 | |||
desi | string (decimal) · numeric | yes | ||
cod_amount | string (int64) · int8 | yes | ||
error | string · text | yes | ||
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Unique: account_id, reference
Checks:
CHECK (((cod_amount IS NULL) OR (cod_amount >= 0)))CHECK ((pieces >= 1))CHECK ((reference ~ '^[A-Za-z0-9-]{4,40}$'::text))
ChannelListing
Table channel_listing · Resource channel_listings
GET /api/v1/channel_listingsGET /api/v1/channel_listings/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
account_id | string (uuid) · uuid | integration_account | ||
external_key | string · text | |||
external_product_id | string · text | yes | ||
external_variant_id | string · text | yes | ||
sku | string · text | yes | ||
barcode | string · text | yes | ||
title | string · text | |||
variant_name | string · text | yes | ||
url | string · text | yes | ||
image_url | string · text | yes | ||
item_variant_id | string (uuid) · uuid | yes | item_variant | |
match_source | string · text | yes | sku, barcode, reference, manual, created | |
remote_quantity | integer · int4 | yes | ||
remote_price | string (int64) · int8 | yes | ||
currency | string · bpchar | yes | max 3 | |
last_pushed_quantity | integer · int4 | yes | ||
last_pushed_at | string (date-time) · timestamptz | yes | ||
push_error | string · text | yes | ||
is_active | boolean · bool | |||
seen_at | string (date-time) · timestamptz | |||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz | |||
meta | object · jsonb |
Primary key: id
Unique: account_id, external_key
Checks:
CHECK ((length(external_key) > 0))CHECK (((remote_price IS NULL) OR (remote_price >= 0)))
EinvoiceDocument
Table einvoice_document · Resource einvoice_documents
GET /api/v1/einvoice_documentsGET /api/v1/einvoice_documents/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
account_id | string (uuid) · uuid | integration_account | ||
order_id | string (uuid) · uuid | yes | sales_order | |
kind | string · text | e_archive, e_invoice, draft | ||
status | string · text | queued, draft, issued, failed, cancelled | ||
external_id | string · text | yes | ||
document_number | string · text | yes | ||
pdf_url | string · text | yes | ||
error | string · text | yes | ||
total_amount | string (int64) · int8 | yes | ||
currency | string · bpchar | yes | max 3 | |
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz | |||
issued_at | string (date-time) · timestamptz | yes |
Primary key: id
IntegrationAccount
Table integration_account · Resource integration_accounts
GET /api/v1/integration_accountsGET /api/v1/integration_accounts/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
provider | string · text | |||
category | string · text | marketplace, ecommerce, payment, shipping, einvoice, messaging, google, microsoft, accounting | ||
label | string · text | |||
channel_id | string (uuid) · uuid | yes | channel | |
warehouse_id | string (uuid) · uuid | yes | warehouse | |
status | string · text | active, paused, error, disconnected | ||
config | object · jsonb | |||
settings | object · jsonb | |||
cursors | object · jsonb | |||
last_sync_at | string (date-time) · timestamptz | yes | ||
last_order_sync_at | string (date-time) · timestamptz | yes | ||
last_product_sync_at | string (date-time) · timestamptz | yes | ||
last_stock_push_at | string (date-time) · timestamptz | yes | ||
last_test_at | string (date-time) · timestamptz | yes | ||
last_test_ok | boolean · bool | yes | ||
last_test_message | string · text | yes | ||
last_error | string · text | yes | ||
consecutive_failures | integer · int4 | |||
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Unique: webhook_token
Checks:
CHECK ((consecutive_failures >= 0))CHECK ((length(TRIM(BOTH FROM label)) > 0))CHECK ((provider ~ '^[a-z][a-z0-9_]{1,40}$'::text))
IntegrationInbound
Table integration_inbound · Resource integration_inbounds
GET /api/v1/integration_inboundsGET /api/v1/integration_inbounds/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
account_id | string (uuid) · uuid | integration_account | ||
external_id | string · text | |||
topic | string · text | yes | ||
status | string · text | accepted, rejected | ||
reason | string · text | yes | ||
received_at | string (date-time) · timestamptz |
Primary key: id
Unique: account_id, external_id
IntegrationIssue
Table integration_issue · Resource integration_issues
GET /api/v1/integration_issuesGET /api/v1/integration_issues/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
account_id | string (uuid) · uuid | integration_account | ||
kind | string · text | unmatched_line, unmatched_listing, stock_short, push_refused, status_unknown, order_failed, auth, rate_limit, config, shipment_failed, einvoice_failed, webhook_rejected | ||
external_ref | string · text | |||
item_variant_id | string (uuid) · uuid | yes | item_variant | |
order_id | string (uuid) · uuid | yes | sales_order | |
message | string · text | |||
detail | object · jsonb | |||
status | string · text | open, resolved, ignored | ||
occurrences | integer · int4 | |||
first_seen_at | string (date-time) · timestamptz | |||
last_seen_at | string (date-time) · timestamptz | |||
resolved_at | string (date-time) · timestamptz | yes | ||
resolved_by | string (uuid) · uuid | yes | app_user |
Primary key: id
Checks:
CHECK ((occurrences >= 1))
IntegrationReview
Table integration_review · Resource integration_reviews
GET /api/v1/integration_reviewsGET /api/v1/integration_reviews/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
account_id | string (uuid) · uuid | integration_account | ||
provider | string · text | |||
external_id | string · text | |||
location_name | string · text | |||
location_title | string · text | yes | ||
rating | integer · int2 | yes | ||
comment | string · text | yes | ||
reviewer_name | string · text | yes | ||
reviewed_at | string (date-time) · timestamptz | yes | ||
reply_text | string · text | yes | ||
replied_at | string (date-time) · timestamptz | yes | ||
reply_status | string · text | none, sending, sent, failed | ||
reply_draft | string · text | yes | ||
reply_error | string · text | yes | ||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK (((length(external_id) >= 1) AND (length(external_id) <= 500)))CHECK ((provider ~ '^[a-z][a-z0-9_]{1,40}$'::text))CHECK (((rating >= 1) AND (rating <= 5)))
IntegrationRun
Table integration_run · Resource integration_runs
GET /api/v1/integration_runsGET /api/v1/integration_runs/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
account_id | string (uuid) · uuid | integration_account | ||
feed | string · text | orders, products, stock, webhook, test, shipments, einvoice, reviews | ||
trigger | string · text | schedule, manual, webhook, era, connect, event | ||
status | string · text | running, ok, partial, failed, skipped | ||
started_at | string (date-time) · timestamptz | |||
finished_at | string (date-time) · timestamptz | yes | ||
counts | object · jsonb | |||
message | string · text | yes | ||
job_id | string (uuid) · uuid | yes | ||
created_by | string (uuid) · uuid | yes | app_user |
Primary key: id
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
integrations.connected | An app account was linked for syncing. | id, provider, category, channelId |
integrations.disconnected | An account stopped syncing for good. | id, provider |
integrations.einvoice_requested | An e-invoice was asked for an order. | id, accountId, orderId |
integrations.einvoice_updated | An e-invoice was issued or failed. | id, orderId, status, documentNumber |
integrations.issue_raised | Something needs a person (unmatched SKU, refused push, …). | id, accountId, kind, message |
integrations.issue_resolved | An issue was resolved or ignored. | id, accountId, kind, status |
integrations.listing_mapped | A listing was tied to an Era product by hand. | id, accountId, itemVariantId |
integrations.listing_publish_requested | Product variants were queued for marketplace publication. | id, accountId, mode, listings, userId |
integrations.listings_synced | Listings were read and matched to products. | id, provider, seen, matched, unmatched |
integrations.paused | An account was paused; nothing syncs. | id, provider |
integrations.resumed | A paused account was switched back on. | id, provider |
integrations.review_replied | The provider published (or refused) a review reply. | id, accountId, ok |
integrations.review_reply_requested | A public reply to a review is on its way to the provider. | id, accountId |
integrations.reviews_synced | Reviews were read from a review provider (Google Business Profile). | id, provider, created, updated |
integrations.run_finished | A sync, push or test finished. | id, accountId, provider, feed, status, counts, message |
integrations.settings_changed | What an account syncs changed. | id, provider |
integrations.shipment_requested | A parcel was booked with a carrier. | id, accountId, orderId, carrier, reference |
integrations.shipment_updated | A parcel’s carrier status changed. | id, orderId, status, trackingNumber |
integrations.stock_pushed | Era’s stock was sent to a channel. | id, provider, pushed, failed |
integrations.sync_requested | A sync was asked for (a person, Era, a webhook or the schedule). | id, provider, feed, trigger, runId |
integrations.tested | A connection test ran. | id, provider, ok, message |
integrations.webhook_received | A provider nudged Era that something changed. | id, accountId, provider, topic |