ReferenceReference by module

Apps API

Every Apps action (14) and data model (1): endpoints, scopes, input, preconditions, events and examples.

The apps and services you connected, and the updates sent to them.

Module code apps · 14 actions · 1 data models · 12 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: apps (everything), apps:read (read only) or apps:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
app_connection_choicesapp_connection_choice/api/v1/app_connection_choices

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

ActionNeedsWhat it does
apps.add_connectionapps:readAdd a connection
apps.connectapps:manageRecord a connection
apps.connect_whatsappapps:readConnect WhatsApp Business
apps.disconnectapps:deleteDisconnect
apps.record_failureapps:handleRecord a connection failure
apps.record_healthyapps:handleRecord a passed connection test
apps.record_pollapps:handleRecord a poll of a connected app
apps.register_installationapps:manageRegister an installation
apps.rotate_secretapps:handleRefresh a connection’s token
apps.set_connection_choiceapps:readChoose which account to use
apps.set_connection_ownerapps:readChange whose a connection is
apps.set_pausedapps:managePause or resume a connection
apps.store_secretapps:manageStore a secret
apps.update_connectionapps:manageChange a connection

apps.add_connection

Add a connection — One more account of an app — yours or the company’s — with its key sealed in the vault.

Needs apps:read · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/actions/apps.add_connection
API key or OAuth token · apps:read

Input

FieldTypeRequiredNotes
providerstringyesApp · max 64
labelstringyesName · max 160
kindstring (enum)yesKind · Allowed values: oauth2, api_key, basic
valuestringKey · max 16000
secretRefstringWhere the secret is kept · max 200
accountRefstringAccount (e.g. info@…) · max 200
ownerstring (enum)Whose · Allowed values: me, workspace
sharestring (enum)Who may use it · Allowed values: me, team, workspace
teamIduuidTeam

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
empty_labelA connection needs a name.
bad_providerAn app is a lowercase slug.
not_managerA company connection is added by an owner or manager.
team_requiredSharing with a team names the team.
no_secretA connection points at a key or a signed-in account.
label_takenTwo live connections of one app cannot share a name.

Events it publishes: apps.connected

What it writes: secret (insert) — Seals the key, when one is given; credential (insert) — Writes the connection with its owner and share

curl -X POST https://app.getera.io/api/v1/actions/apps.add_connection \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"provider":"App","label":"Name","kind":"oauth2"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connected"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.add_connection", {
  provider: "App",
  label: "Name",
  kind: "oauth2"
})
era apps add_connection \
  --provider "App" \
  --label "Name" \
  --kind "oauth2"
tools/call
{
  "name": "era__apps__add_connection",
  "arguments": {
    "provider": "App",
    "label": "Name",
    "kind": "oauth2"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_label, bad_provider, not_manager, team_required, no_secret, label_taken · 429 slow down

apps.connect

Record a connection — Writes the one row that says this workspace is connected to something.

Needs apps:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/actions/apps.connect
API key or OAuth token · apps:manage

Input

FieldTypeRequiredNotes
providerstringyesProvider · max 64
labelstringyesName · max 160
kindstring (enum)yesKind · Allowed values: oauth2, api_key, basic, mcp, signing
secretRefstringyesWhere the secret is kept · The name of the stored secret — never the secret itself. Nothing here is ever a token. · max 200
accountRefstringAccount at the provider · max 200
scopesobject / arrayScopes granted
expiresAtdate-timeExpires

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
empty_labelA connection needs a name.
bad_providerA provider is a lowercase slug.
bad_scopesScopes are a list of strings.
label_takenTwo live connections to one provider cannot share a name.

Events it publishes: apps.connected

What it writes: credential (insert) — Writes the connection. Never the secret

curl -X POST https://app.getera.io/api/v1/actions/apps.connect \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"provider":"Provider","label":"Name","kind":"oauth2","secretRef":"Where the secret is kept"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connected"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.connect", {
  provider: "Provider",
  label: "Name",
  kind: "oauth2",
  secretRef: "Where the secret is kept"
})
era apps connect \
  --provider "Provider" \
  --label "Name" \
  --kind "oauth2" \
  --secret-ref "Where the secret is kept"
tools/call
{
  "name": "era__apps__connect",
  "arguments": {
    "provider": "Provider",
    "label": "Name",
    "kind": "oauth2",
    "secretRef": "Where the secret is kept"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_label, bad_provider, bad_scopes, label_taken · 429 slow down

apps.connect_whatsapp

Connect WhatsApp Business — Records a WhatsApp Business number (from Meta’s sign-up) as your or the company’s connection, with its token sealed in the vault.

Needs apps:read · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/actions/apps.connect_whatsapp
API key or OAuth token · apps:read

Input

FieldTypeRequiredNotes
phoneNumberIdstringyesPhone number id · max 40
wabaIdstringyesWhatsApp Business Account id · max 40
businessIdstringBusiness portfolio id · max 40
displayNumberstringyesNumber · max 40
verifiedNamestringDisplay name · max 200
tokenstringyesBusiness token · max 4000
pinstringTwo-step PIN · max 6
viastring (enum)Connected by · Allowed values: embedded_signup, manual
ownerstring (enum)Whose · Allowed values: me, workspace
sharestring (enum)Who may use it · Allowed values: me, team, workspace
teamIduuidTeam

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bad_whatsapp_idsThe ids are Meta’s numeric ids and the number is a full international number.
not_managerThe company’s WhatsApp is added by an owner or manager.
team_requiredSharing with a team names the team.
whatsapp_number_takenA number that is a colleague’s personal connection (or the company’s, for a non-manager) is not taken over.

Events it publishes: apps.connected, apps.connection_updated

What it writes: secret (upsert) — Seals the business token (and the PIN), rotating in place on a reconnect; credential (upsert) — One connection per number, with its owner and Meta ids

curl -X POST https://app.getera.io/api/v1/actions/apps.connect_whatsapp \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"phoneNumberId":"Phone number id","wabaId":"WhatsApp Business Account id","displayNumber":"Number","token":"Business token"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connected",
    "apps.connection_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.connect_whatsapp", {
  phoneNumberId: "Phone number id",
  wabaId: "WhatsApp Business Account id",
  displayNumber: "Number",
  token: "Business token"
})
era apps connect_whatsapp \
  --phone-number-id "Phone number id" \
  --waba-id "WhatsApp Business Account id" \
  --display-number "Number" \
  --token "Business token"
tools/call
{
  "name": "era__apps__connect_whatsapp",
  "arguments": {
    "phoneNumberId": "Phone number id",
    "wabaId": "WhatsApp Business Account id",
    "displayNumber": "Number",
    "token": "Business token"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_whatsapp_ids, not_manager, team_required, whatsapp_number_taken · 429 slow down

apps.disconnect

Disconnect — Hides the connection and marks it revoked. Anything signing with it starts failing.

Needs apps:delete · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/actions/apps.disconnect
API key or OAuth token · apps:delete

Input

FieldTypeRequiredNotes
iduuidyesConnection · id of credential

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist in this workspace.

Events it publishes: apps.disconnected

What it writes: credential (update) — Writes the tombstone, through sql/057’s door

curl -X POST https://app.getera.io/api/v1/actions/apps.disconnect \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id":"CREDENTIAL_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.disconnected"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.disconnect", {
  id: "CREDENTIAL_ID"
})
era apps disconnect \
  --id "CREDENTIAL_ID"
tools/call
{
  "name": "era__apps__disconnect",
  "arguments": {
    "id": "CREDENTIAL_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

apps.record_failure

Record a connection failure — Keeps the provider’s own complaint on the connection, verbatim.

Needs apps:handle · changes data

POSThttps://app.getera.io/api/v1/actions/apps.record_failure
API key or OAuth token · apps:handle

Input

FieldTypeRequiredNotes
iduuidyesConnection · id of credential
messagestringyesWhat the provider said · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist in this workspace.

Events it publishes: apps.connection_failed

What it writes: credential (update) — Marks it in error and keeps the message

curl -X POST https://app.getera.io/api/v1/actions/apps.record_failure \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id":"CREDENTIAL_ID","message":"What the provider said"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connection_failed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.record_failure", {
  id: "CREDENTIAL_ID",
  message: "What the provider said"
})
era apps record_failure \
  --id "CREDENTIAL_ID" \
  --message "What the provider said"
tools/call
{
  "name": "era__apps__record_failure",
  "arguments": {
    "id": "CREDENTIAL_ID",
    "message": "What the provider said"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

apps.record_healthy

Record a passed connection test — Clears the error on a connection whose test passed, and stamps when.

Needs apps:handle · changes data

POSThttps://app.getera.io/api/v1/actions/apps.record_healthy
API key or OAuth token · apps:handle

Input

FieldTypeRequiredNotes
iduuidyesConnection · id of credential

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist in this workspace.

Events it publishes: apps.connection_healthy

What it writes: credential (update) — Back to active, error cleared, test time stamped

curl -X POST https://app.getera.io/api/v1/actions/apps.record_healthy \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id":"CREDENTIAL_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connection_healthy"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.record_healthy", {
  id: "CREDENTIAL_ID"
})
era apps record_healthy \
  --id "CREDENTIAL_ID"
tools/call
{
  "name": "era__apps__record_healthy",
  "arguments": {
    "id": "CREDENTIAL_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

apps.record_poll

Record a poll of a connected app — Moves the checkpoint of one feed of an installation and admits each new item once, as an apps.app_event.

Needs apps:handle · changes data

POSThttps://app.getera.io/api/v1/actions/apps.record_poll
API key or OAuth token · apps:handle

Input

FieldTypeRequiredNotes
credentialIduuidyesConnection · id of credential
feedstringyesFeed · max 64
cursorstringCheckpoint · max 2000
itemsobject / arrayNew items
errorstringWhat the provider said · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist in this workspace.
invalid_feedA feed name is lowercase letters, digits and underscores.

Events it publishes: apps.app_event, apps.poll_failed, apps.connection_failed

What it writes: app_poll_state (upsert) — Moves the checkpoint, or counts the failure; app_poll_seen (insert) — Admits each item id once per feed; credential (update) — Stamps last use; goes red after three failures in a row

curl -X POST https://app.getera.io/api/v1/actions/apps.record_poll \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"credentialId":"CREDENTIAL_ID","feed":"Feed"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.app_event",
    "apps.poll_failed",
    "apps.connection_failed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.record_poll", {
  credentialId: "CREDENTIAL_ID",
  feed: "Feed"
})
era apps record_poll \
  --credential-id "CREDENTIAL_ID" \
  --feed "Feed"
tools/call
{
  "name": "era__apps__record_poll",
  "arguments": {
    "credentialId": "CREDENTIAL_ID",
    "feed": "Feed"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, invalid_feed · 429 slow down

apps.register_installation

Register an installation — Adopts a connector, OAuth account or MCP server the engine connected as this workspace’s installation record, so it can be tested, paused and uninstalled in one place.

Needs apps:manage · changes data

POSThttps://app.getera.io/api/v1/actions/apps.register_installation
API key or OAuth token · apps:manage

Input

FieldTypeRequiredNotes
sourcestring (enum)yesWhere it was connected · Allowed values: connector, oauth, mcp_server, engine_credential
sourceRefstringyesEngine row · max 200
providerstringyesProvider · max 64
labelstringyesName · max 160
kindstring (enum)yesKind · Allowed values: oauth2, api_key, basic, mcp, signing
accountRefstringAccount at the provider · max 200
scopesobject / arrayScopes granted

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
empty_labelA connection needs a name.
bad_providerA provider is a lowercase slug.
bad_scopesScopes are a list of strings.

Events it publishes: apps.connected

What it writes: credential (insert) — Writes the installation row, or returns the existing one

curl -X POST https://app.getera.io/api/v1/actions/apps.register_installation \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"source":"connector","sourceRef":"Engine row","provider":"Provider","label":"Name","kind":"oauth2"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connected"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.register_installation", {
  source: "connector",
  sourceRef: "Engine row",
  provider: "Provider",
  label: "Name",
  kind: "oauth2"
})
era apps register_installation \
  --source "connector" \
  --source-ref "Engine row" \
  --provider "Provider" \
  --label "Name" \
  --kind "oauth2"
tools/call
{
  "name": "era__apps__register_installation",
  "arguments": {
    "source": "connector",
    "sourceRef": "Engine row",
    "provider": "Provider",
    "label": "Name",
    "kind": "oauth2"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_label, bad_provider, bad_scopes · 429 slow down

apps.rotate_secret

Refresh a connection’s token — Re-seals a refreshed OAuth token over the same vault entry — the worker’s verb, narrower than store_secret.

Needs apps:handle · changes data

POSThttps://app.getera.io/api/v1/actions/apps.rotate_secret
API key or OAuth token · apps:handle

Input

FieldTypeRequiredNotes
credentialIduuidyesConnection · id of credential
valuestringyesSecret · max 16000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist.
no_vault_secretOnly a secret already in the vault is rotated.

Events it publishes: apps.connection_updated

What it writes: secret (update) — Rotates the sealed value in place

curl -X POST https://app.getera.io/api/v1/actions/apps.rotate_secret \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"credentialId":"CREDENTIAL_ID","value":"Secret"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connection_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.rotate_secret", {
  credentialId: "CREDENTIAL_ID",
  value: "Secret"
})
era apps rotate_secret \
  --credential-id "CREDENTIAL_ID" \
  --value "Secret"
tools/call
{
  "name": "era__apps__rotate_secret",
  "arguments": {
    "credentialId": "CREDENTIAL_ID",
    "value": "Secret"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, no_vault_secret · 429 slow down

apps.set_connection_choice

Choose which account to use — What an Era, a workflow or your chats use for an app: your own (Run as), the company’s, or one by name.

Needs apps:read · changes data

POSThttps://app.getera.io/api/v1/app_connection_choices/actions/set_connection_choice
API key or OAuth token · apps:read
  • Canonical address: POST /api/v1/actions/apps.set_connection_choice

Input

FieldTypeRequiredNotes
subjectTypestring (enum)yesFor · Allowed values: chat, agent, workflow, voice_agent
subjectIdstringWhich one · max 200
familystringyesApp · max 64
pickstring (enum)yesUse · Allowed values: mine, workspace, connection
connectionIduuidConnection · id of credential

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
subject_requiredAn Era, workflow or voice agent is named.
connection_requiredA named pick names the connection.
connection_not_foundThe connection must exist in this workspace.
provider_mismatchThe connection must be of the same app.
connection_not_usableOnly a connection the chooser may use can be picked.

Events it publishes: apps.connection_choice_set

What it writes: app_connection_choice (upsert) — One row per thing and app

curl -X POST https://app.getera.io/api/v1/app_connection_choices/actions/set_connection_choice \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"subjectType":"chat","family":"App","pick":"mine"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connection_choice_set"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appConnectionChoices.setConnectionChoice({
  subjectType: "chat",
  family: "App",
  pick: "mine"
})
era app_connection_choices set_connection_choice \
  --subject-type "chat" \
  --family "App" \
  --pick "mine"
tools/call
{
  "name": "era__apps__set_connection_choice",
  "arguments": {
    "subjectType": "chat",
    "family": "App",
    "pick": "mine"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 subject_required, connection_required, connection_not_found, provider_mismatch, connection_not_usable · 429 slow down

apps.set_connection_owner

Change whose a connection is — Personal or company, and who else may use it. Only a personal connection’s owner may change it.

Needs apps:read · changes data

POSThttps://app.getera.io/api/v1/actions/apps.set_connection_owner
API key or OAuth token · apps:read

Input

FieldTypeRequiredNotes
iduuidyesConnection · id of credential
ownerstring (enum)yesWhose · Allowed values: me, workspace
sharestring (enum)Who may use it · Allowed values: me, team, workspace
teamIduuidTeam

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist in this workspace.
not_ownerOnly the owner of a personal connection changes it.
not_managerCompany connections are changed by an owner or manager.
workspace_ownedA company connection never becomes someone’s personal one.
team_requiredSharing with a team names the team.

Events it publishes: apps.connection_updated

What it writes: credential (update) — Sets owner_user_id, share_scope and share_team_id

curl -X POST https://app.getera.io/api/v1/actions/apps.set_connection_owner \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id":"CREDENTIAL_ID","owner":"me"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connection_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.set_connection_owner", {
  id: "CREDENTIAL_ID",
  owner: "me"
})
era apps set_connection_owner \
  --id "CREDENTIAL_ID" \
  --owner "me"
tools/call
{
  "name": "era__apps__set_connection_owner",
  "arguments": {
    "id": "CREDENTIAL_ID",
    "owner": "me"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_owner, not_manager, workspace_owned, team_required · 429 slow down

apps.set_paused

Pause or resume a connection — A paused connection keeps its secret and history; chats, agents and workflows skip it.

Needs apps:manage · changes data

POSThttps://app.getera.io/api/v1/actions/apps.set_paused
API key or OAuth token · apps:manage

Input

FieldTypeRequiredNotes
iduuidyesConnection · id of credential
pausedbooleanyesPaused

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist in this workspace.
not_pausableA revoked or expired connection is reconnected, not paused.
no_secretA connection that points at no secret cannot be resumed.

Events it publishes: apps.paused, apps.resumed

What it writes: credential (update) — Flips the status between paused and active

curl -X POST https://app.getera.io/api/v1/actions/apps.set_paused \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id":"CREDENTIAL_ID","paused":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.paused",
    "apps.resumed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.set_paused", {
  id: "CREDENTIAL_ID",
  paused: true
})
era apps set_paused \
  --id "CREDENTIAL_ID" \
  --paused true
tools/call
{
  "name": "era__apps__set_paused",
  "arguments": {
    "id": "CREDENTIAL_ID",
    "paused": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_pausable, no_secret · 429 slow down

apps.store_secret

Store a secret — Seals a key, token or password in the vault. It can be replaced, never read back.

Needs apps:manage · changes data

POSThttps://app.getera.io/api/v1/actions/apps.store_secret
API key or OAuth token · apps:manage

Input

FieldTypeRequiredNotes
credentialIduuidConnection · id of credential
valuestringyesSecret · max 16000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist in this workspace.

Events it publishes: apps.connection_updated

What it writes: secret (upsert) — Seals the value; a second call rotates it in place; credential (update) — Points the connection at the vault secret

curl -X POST https://app.getera.io/api/v1/actions/apps.store_secret \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"value":"Secret"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connection_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.store_secret", {
  value: "Secret"
})
era apps store_secret \
  --value "Secret"
tools/call
{
  "name": "era__apps__store_secret",
  "arguments": {
    "value": "Secret"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

apps.update_connection

Change a connection — Renames it, repoints it at a different stored secret, or marks it expired.

Needs apps:manage · changes data

POSThttps://app.getera.io/api/v1/actions/apps.update_connection
API key or OAuth token · apps:manage

Input

FieldTypeRequiredNotes
iduuidyesConnection · id of credential
labelstringName · max 160
secretRefstringWhere the secret is kept · The name of the stored secret — never the secret itself. Nothing here is ever a token. · max 200
accountRefstringAccount at the provider · max 200
scopesobject / arrayScopes granted
expiresAtdate-timeExpires
statusstring (enum)Status · Allowed values: active, expired, revoked, error

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe connection must exist in this workspace.
empty_labelA connection needs a name.
bad_scopesScopes are a list of strings.

Events it publishes: apps.connection_updated

What it writes: credential (update) — Rewrites the connection

curl -X POST https://app.getera.io/api/v1/actions/apps.update_connection \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id":"CREDENTIAL_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "apps.connection_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("apps.update_connection", {
  id: "CREDENTIAL_ID"
})
era apps update_connection \
  --id "CREDENTIAL_ID"
tools/call
{
  "name": "era__apps__update_connection",
  "arguments": {
    "id": "CREDENTIAL_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold apps:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, empty_label, bad_scopes · 429 slow down

Data models

AppConnectionChoice

Table app_connection_choice · Resource app_connection_choices

  • GET /api/v1/app_connection_choices
  • GET /api/v1/app_connection_choices/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
subject_typestring · textchat, agent, workflow, voice_agent
subject_idstring · text
familystring · text
pickstring · textmine, workspace, connection
connection_idstring (uuid) · uuidyescredential
created_bystring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Unique: workspace_id, subject_type, subject_id, family

Checks:

  • CHECK ((family ~ '^[a-z][a-z0-9_]{1,63}$'::text))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
apps.app_eventA connected app reported something new (polled, admitted once).id, provider, feed, external_id, occurred_at, summary, item
apps.connectedThis workspace connected something.id, provider, kind
apps.connection_choice_setAn Era, workflow or chat was told which account of an app to use.subject_type, subject_id, family, pick
apps.connection_failedA provider refused a connection.id, provider
apps.connection_healthyA connection test passed.id, provider
apps.connection_updatedA connection was renamed or repointed.id, provider
apps.deliveredAn outbound webhook was delivered.webhook_id, event_name
apps.delivery_failedAn outbound webhook could not be delivered.webhook_id, event_name, status
apps.disconnectedA connection was revoked.id, provider
apps.pausedA person paused a connection; the runtime skips it.id, provider
apps.poll_failedA connected app refused a poll.id, provider, feed, failures, message
apps.resumedA paused connection was switched back on.id, provider