ReferenceReference by module

Quotes API

Every Quotes action (13) and data model (5): endpoints, scopes, input, preconditions, events and examples.

Price quotes with a PDF and a link the customer can accept or decline.

Module code quotes · 13 actions · 5 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: quotes (everything), quotes:read (read only) or quotes:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
quote_eventsquote_event/api/v1/quote_events
quote_linesquote_line/api/v1/quote_lines
quote_templatesquote_template/api/v1/quote_templates
quote_versionsquote_version/api/v1/quote_versions
quotesquote/api/v1/quotesquotes.create

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
quotes.acceptquotes:writeMark accepted
quotes.add_notequotes:writeAdd a note
quotes.createquotes:writeStart a quote
quotes.create_templatequotes:manageSave a quote template
quotes.delete_templatequotes:manageDelete a quote template
quotes.duplicatequotes:writeDuplicate
quotes.expire_duequotes:writeLapse overdue quotes
quotes.rejectquotes:writeMark declined
quotes.revisequotes:writeRevise into a new version
quotes.sendquotes:writeSend by email
quotes.update_draftquotes:writeEdit a draft quote
quotes.update_templatequotes:manageEdit a quote template
quotes.withdrawquotes:writeWithdraw

quotes.accept

Mark accepted — Records the customer’s yes on the current version; finance drafts the agreement from it.

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

POSThttps://app.getera.io/api/v1/quotes/{id}/actions/accept
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.accept

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Quote · id of quote
namestringAnswered by · max 200
notestringNote · max 4000
viastring (enum)How · Allowed values: manual, link
signatureNamestringTyped signature · max 200
ipstringFrom address · max 64
userAgentstringBrowser · max 400

Preconditions

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

CodeRefused when
quote_not_foundThe quote must exist.
quote_not_openOnly a sent quote is answered.
quote_validity_passedA quote past its valid-until day is not accepted.
quote_signature_requiredAccepting through the link needs the customer’s typed name.

Events it publishes: quotes.accepted

What it writes: quote (update) — Marks it accepted, freezes the accepted version and keeps the typed signature; quote_event (insert) — Records the answer

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quotes.accept("QUOTE_ID", {
  name: "Answered by"
})
era quotes accept QUOTE_ID \
  --name "Answered by"
tools/call
{
  "name": "era__quotes__accept",
  "arguments": {
    "id": "QUOTE_ID",
    "name": "Answered by"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold quotes:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 quote_not_found, quote_not_open, quote_validity_passed, quote_signature_required · 429 slow down

quotes.add_note

Add a note — A line in the quote’s own history.

Needs quotes:write · changes data

POSThttps://app.getera.io/api/v1/quote_events/actions/add_note
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.add_note

Input

FieldTypeRequiredNotes
iduuidyesQuote · id of quote
messagestringyesNote · max 4000

Preconditions

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

CodeRefused when
quote_not_foundThe quote must exist.

Events it publishes: quotes.note_added

What it writes: quote_event (insert) — Records the note

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quoteEvents.addNote({
  id: "QUOTE_ID",
  message: "Note"
})
era quote_events add_note \
  --id "QUOTE_ID" \
  --message "Note"
tools/call
{
  "name": "era__quotes__add_note",
  "arguments": {
    "id": "QUOTE_ID",
    "message": "Note"
  }
}

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

quotes.create

Start a quote — A numbered draft quote with its first version and lines, each with a frozen tax snapshot.

Needs quotes:write · changes data

POSThttps://app.getera.io/api/v1/quotes/actions/create
API key or OAuth token · quotes:write
  • REST alias: POST /api/v1/quotes
  • Canonical address: POST /api/v1/actions/quotes.create

Input

FieldTypeRequiredNotes
customerIduuidFor · id of customer
leadIduuidLead
templateIduuidFrom template · id of quote_template
titlestringTitle · max 200
currencystringCurrency · max 3
taxCountrystringTax country · max 2
validUntildateValid until
notesstringNotes · max 4000
linesobject / arrayLines

Preconditions

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

CodeRefused when
customer_not_foundThe person must exist.
quote_template_not_foundA named template must exist.
customer_archivedAn archived person is not quoted.
invalid_currencyThe currency must be an ISO-4217 code.
quote_needs_linesA quote needs at least one line.
invalid_quote_linesEvery line needs a description, a positive quantity and a price; a catalog item must exist.

Events it publishes: quotes.created

What it writes: quote (insert) — Opens the quote with its number; quote_version (insert) — Writes version 1; quote_line (insert) — Writes its lines with their tax snapshot; quote_event (insert) — Records that it was created

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quotes.create({
  customerId: "CUSTOMER_ID"
})
era quotes create \
  --customer-id "CUSTOMER_ID"
tools/call
{
  "name": "era__quotes__create",
  "arguments": {
    "customerId": "CUSTOMER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold quotes:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 customer_not_found, quote_template_not_found, customer_archived, invalid_currency, quote_needs_lines, invalid_quote_lines · 429 slow down

quotes.create_template

Save a quote template — A reusable title, notes and lines — typed in, or saved from a quote.

Needs quotes:manage · changes data

POSThttps://app.getera.io/api/v1/quote_templates/actions/create_template
API key or OAuth token · quotes:manage
  • Canonical address: POST /api/v1/actions/quotes.create_template

Input

FieldTypeRequiredNotes
namestringyesTemplate name · max 120
titlestringQuote title · max 200
notesstringNotes for the customer · max 4000
currencystringCurrency · max 3
linesobject / arrayLines
fromQuoteIduuidFrom quote · id of quote

Preconditions

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

CodeRefused when
quote_template_name_takenTemplate names are unique in a workspace.
quote_not_foundThe quote must exist.
invalid_currencyThe currency must be an ISO-4217 code.
quote_needs_linesA quote needs at least one line.
invalid_quote_linesEvery line needs a description, a positive quantity and a price; a catalog item must exist.

Events it publishes: quotes.template_changed

What it writes: quote_template (insert) — Saves the template; quote_event (insert) — Notes on the quote that it was saved as a template

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quoteTemplates.createTemplate({
  name: "Template name"
})
era quote_templates create_template \
  --name "Template name"
tools/call
{
  "name": "era__quotes__create_template",
  "arguments": {
    "name": "Template name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold quotes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 quote_template_name_taken, quote_not_found, invalid_currency, quote_needs_lines, invalid_quote_lines · 429 slow down

quotes.delete_template

Delete a quote template — Removes a template; quotes made from it are untouched.

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

POSThttps://app.getera.io/api/v1/quote_templates/{id}/actions/delete_template
API key or OAuth token · quotes:manage
  • Canonical address: POST /api/v1/actions/quotes.delete_template

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Template · id of quote_template

Preconditions

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

CodeRefused when
quote_template_not_foundThe template must exist.

Events it publishes: quotes.template_changed

What it writes: quote_template (delete) — Deletes the template

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quoteTemplates.deleteTemplate("QUOTE_TEMPLATE_ID")
era quote_templates delete_template QUOTE_TEMPLATE_ID
tools/call
{
  "name": "era__quotes__delete_template",
  "arguments": {
    "id": "QUOTE_TEMPLATE_ID"
  }
}

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

quotes.duplicate

Duplicate — A new draft quote with the same lines — for the same person or another one.

Needs quotes:write · changes data

POSThttps://app.getera.io/api/v1/quotes/{id}/actions/duplicate
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.duplicate

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Quote · id of quote
customerIduuidFor · id of customer

Preconditions

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

CodeRefused when
quote_not_foundThe quote must exist.
customer_not_foundThe person must exist.
customer_archivedAn archived person is not quoted.
quote_needs_linesA quote needs at least one line.
invalid_quote_linesEvery line needs a description, a positive quantity and a price; a catalog item must exist.

Events it publishes: quotes.created, quotes.duplicated

What it writes: quote (insert) — Opens the copy with its own number; quote_version (insert) — Writes version 1; quote_line (insert) — Copies the lines, re-priced; quote_event (insert) — Records where it came from; quote (update) — Remembers the original

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quotes.duplicate("QUOTE_ID", {
  customerId: "CUSTOMER_ID"
})
era quotes duplicate QUOTE_ID \
  --customer-id "CUSTOMER_ID"
tools/call
{
  "name": "era__quotes__duplicate",
  "arguments": {
    "id": "QUOTE_ID",
    "customerId": "CUSTOMER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold quotes:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 quote_not_found, customer_not_found, customer_archived, quote_needs_lines, invalid_quote_lines · 429 slow down

quotes.expire_due

Lapse overdue quotes — Machine-only daily routine: a sent quote past its valid-until day becomes expired.

Needs quotes:write · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/quote_events/actions/expire_due
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.expire_due

Input

This action takes no input.

Events it publishes: quotes.expired

What it writes: quote (update) — Marks overdue quotes expired; quote_event (insert) — Records the lapse on each

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

quotes.reject

Mark declined — Records the customer’s no, with their reason when they gave one.

Needs quotes:write · changes data

POSThttps://app.getera.io/api/v1/quotes/{id}/actions/reject
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.reject

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Quote · id of quote
namestringAnswered by · max 200
notestringNote · max 4000
viastring (enum)How · Allowed values: manual, link
signatureNamestringTyped signature · max 200
ipstringFrom address · max 64
userAgentstringBrowser · max 400

Preconditions

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

CodeRefused when
quote_not_foundThe quote must exist.
quote_not_openOnly a sent quote is answered.

Events it publishes: quotes.rejected

What it writes: quote (update) — Marks it declined; quote_event (insert) — Records the answer

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quotes.reject("QUOTE_ID", {
  name: "Answered by"
})
era quotes reject QUOTE_ID \
  --name "Answered by"
tools/call
{
  "name": "era__quotes__reject",
  "arguments": {
    "id": "QUOTE_ID",
    "name": "Answered by"
  }
}

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

quotes.revise

Revise into a new version — Copies the sent version into a new draft version; the old link stops answering.

Needs quotes:write · changes data

POSThttps://app.getera.io/api/v1/quotes/{id}/actions/revise
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.revise

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Quote · id of quote

Preconditions

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

CodeRefused when
quote_not_foundThe quote must exist.
quote_not_revisableOnly a sent, declined or lapsed quote is revised.

Events it publishes: quotes.revised

What it writes: quote_version (insert) — Writes the new version; quote_line (insert) — Copies the lines; quote (update) — Points at the new version, back to draft; quote_event (insert) — Records the revision

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quotes.revise("QUOTE_ID")
era quotes revise QUOTE_ID
tools/call
{
  "name": "era__quotes__revise",
  "arguments": {
    "id": "QUOTE_ID"
  }
}

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

quotes.send

Send by email — Emails the current version with a link to accept or decline it; the record shows whether it went.

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

POSThttps://app.getera.io/api/v1/quotes/{id}/actions/send
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.send

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Quote · id of quote
tostringEmail · max 320
messagestringMessage · max 4000

Preconditions

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

CodeRefused when
quote_not_foundThe quote must exist.
quote_not_sendableOnly a draft or an open quote is sent.
quote_validity_passedA quote past its valid-until day is not sent.
quote_needs_linesA quote needs at least one line.
quote_recipient_missingThere must be an address to send to.

Events it publishes: quotes.send_requested, quotes.sent

What it writes: quote (update) — Marks it sent and mints its answer link; quote_version (update) — Stamps when this version was sent; quote_event (insert) — Records who it was sent to

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quotes.send("QUOTE_ID", {
  to: "Email"
})
era quotes send QUOTE_ID \
  --to "Email"
tools/call
{
  "name": "era__quotes__send",
  "arguments": {
    "id": "QUOTE_ID",
    "to": "Email"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold quotes:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 quote_not_found, quote_not_sendable, quote_validity_passed, quote_needs_lines, quote_recipient_missing · 429 slow down

quotes.update_draft

Edit a draft quote — Changes the title, lines, validity or notes — only while the current version is a draft.

Needs quotes:write · changes data

POSThttps://app.getera.io/api/v1/quotes/{id}/actions/update_draft
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.update_draft

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Quote · id of quote
titlestringTitle · max 200
validUntildateValid until
notesstringNotes · max 4000
taxCountrystringTax country · max 2
linesobject / arrayLines

Preconditions

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

CodeRefused when
quote_not_foundThe quote must exist.
quote_not_draftOnly a draft is edited.
quote_needs_linesA quote needs at least one line.
invalid_quote_linesEvery line needs a description, a positive quantity and a price; a catalog item must exist.

Events it publishes: quotes.updated

What it writes: quote (update) — Updates the title; quote_version (update) — Updates validity, notes and totals; quote_line (upsert) — Rewrites the lines; quote_event (insert) — Records the edit

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quotes.updateDraft("QUOTE_ID", {
  title: "Title"
})
era quotes update_draft QUOTE_ID \
  --title "Title"
tools/call
{
  "name": "era__quotes__update_draft",
  "arguments": {
    "id": "QUOTE_ID",
    "title": "Title"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold quotes:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 quote_not_found, quote_not_draft, quote_needs_lines, invalid_quote_lines · 429 slow down

quotes.update_template

Edit a quote template — Changes a template; quotes already made from it keep their own copy.

Needs quotes:manage · changes data

POSThttps://app.getera.io/api/v1/quote_templates/{id}/actions/update_template
API key or OAuth token · quotes:manage
  • Canonical address: POST /api/v1/actions/quotes.update_template

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Template · id of quote_template
namestringTemplate name · max 120
titlestringQuote title · max 200
notesstringNotes for the customer · max 4000
currencystringCurrency · max 3
linesobject / arrayLines

Preconditions

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

CodeRefused when
quote_template_not_foundThe template must exist.
quote_template_name_takenTemplate names are unique in a workspace.
quote_needs_linesA quote needs at least one line.
invalid_quote_linesEvery line needs a description, a positive quantity and a price; a catalog item must exist.

Events it publishes: quotes.template_changed

What it writes: quote_template (update) — Rewrites the template

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quoteTemplates.updateTemplate("QUOTE_TEMPLATE_ID", {
  name: "Template name"
})
era quote_templates update_template QUOTE_TEMPLATE_ID \
  --name "Template name"
tools/call
{
  "name": "era__quotes__update_template",
  "arguments": {
    "id": "QUOTE_TEMPLATE_ID",
    "name": "Template name"
  }
}

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

quotes.withdraw

Withdraw — Takes a draft or an open quote off the table; its link stops answering.

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

POSThttps://app.getera.io/api/v1/quotes/{id}/actions/withdraw
API key or OAuth token · quotes:write
  • Canonical address: POST /api/v1/actions/quotes.withdraw

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Quote · id of quote
reasonstringReason · max 2000

Preconditions

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

CodeRefused when
quote_not_foundThe quote must exist.
quote_not_withdrawableOnly a draft or an open quote is withdrawn.

Events it publishes: quotes.withdrawn

What it writes: quote (update) — Marks it withdrawn; quote_event (insert) — Records why

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.quotes.withdraw("QUOTE_ID", {
  reason: "Reason"
})
era quotes withdraw QUOTE_ID \
  --reason "Reason"
tools/call
{
  "name": "era__quotes__withdraw",
  "arguments": {
    "id": "QUOTE_ID",
    "reason": "Reason"
  }
}

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

Data models

QuoteEvent

Table quote_event · Resource quote_events

  • GET /api/v1/quote_events
  • GET /api/v1/quote_events/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
quote_idstring (uuid) · uuidquote
typestring · textcreated, updated, revised, send_requested, accepted, rejected, expired, withdrawn, note_added, duplicated, template_saved
messagestring · textyes
parametersobject · jsonb
actor_user_idstring (uuid) · uuidyesapp_user
occurred_atstring (date-time) · timestamptz

Primary key: id

QuoteLine

Table quote_line · Resource quote_lines

  • GET /api/v1/quote_lines
  • GET /api/v1/quote_lines/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
quote_idstring (uuid) · uuidquote
version_idstring (uuid) · uuidquote_version
descriptionstring · text
quantitystring (decimal) · numeric
item_variant_idstring (uuid) · uuidyesitem_variant
currencystring · bpcharmax 3
unit_amountstring (int64) · int8
discount_amountstring (int64) · int8
tax_class_idstring (uuid) · uuidyestax_class
tax_class_namestring · textyes
tax_ratestring (decimal) · numeric
prices_entered_with_taxboolean · bool
net_amountstring (int64) · int8
tax_amountstring (int64) · int8
total_amountstring (int64) · int8
display_orderinteger · int4
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK ((discount_amount >= 0))
  • CHECK ((net_amount >= 0))
  • CHECK ((quantity > (0)::numeric))
  • CHECK ((tax_amount >= 0))
  • CHECK (((tax_rate >= (0)::numeric) AND (tax_rate <= (100)::numeric)))
  • CHECK ((total_amount >= 0))
  • CHECK ((unit_amount >= 0))

QuoteTemplate

Table quote_template · Resource quote_templates

  • GET /api/v1/quote_templates
  • GET /api/v1/quote_templates/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
titlestring · text
notesstring · textyes
currencystring · bpcharyesmax 3
linesobject · jsonb
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((currency IS NULL) OR (currency ~ '^[A-Z]{3}$'::text)))
  • CHECK ((jsonb_typeof(lines) = 'array'::text))
  • CHECK ((length(TRIM(BOTH FROM name)) > 0))
  • CHECK ((length(TRIM(BOTH FROM title)) > 0))

QuoteVersion

Table quote_version · Resource quote_versions

  • GET /api/v1/quote_versions
  • GET /api/v1/quote_versions/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
quote_idstring (uuid) · uuidquote
version_nointeger · int4
currencystring · bpcharmax 3
tax_countrystring · bpcharyesmax 2
valid_untilstring (date) · dateyes
notesstring · textyes
subtotal_amountstring (int64) · int8
discount_total_amountstring (int64) · int8
tax_total_amountstring (int64) · int8
total_amountstring (int64) · int8
sent_atstring (date-time) · timestamptzyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Unique: quote_id, version_no

Checks:

  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK ((discount_total_amount >= 0))
  • CHECK ((subtotal_amount >= 0))
  • CHECK (((tax_country IS NULL) OR (tax_country ~ '^[A-Z]{2}$'::text)))
  • CHECK ((tax_total_amount >= 0))
  • CHECK ((total_amount >= 0))
  • CHECK ((version_no >= 1))

Quote

Table quote · Resource quotes

  • GET /api/v1/quotes
  • GET /api/v1/quotes/{id}
  • POST /api/v1/quotes → quotes.create
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
numberstring · text
titlestring · text
statusstring · textdraft, sent, accepted, rejected, expired, withdrawn
customer_idstring (uuid) · uuidcustomer
lead_idstring (uuid) · uuidyes
owner_user_idstring (uuid) · uuidyesapp_user
current_version_idstring (uuid) · uuidyesquote_version
accepted_version_idstring (uuid) · uuidyesquote_version
link_expires_atstring (date-time) · timestamptzyes
last_viewed_atstring (date-time) · timestamptzyes
sent_atstring (date-time) · timestamptzyes
responded_atstring (date-time) · timestamptzyes
response_namestring · textyes
response_notestring · textyes
responded_viastring · textyes
withdrawn_atstring (date-time) · timestamptzyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
signature_namestring · textyes
signed_atstring (date-time) · timestamptzyes
signature_ipstring · textyes
signature_user_agentstring · textyes
duplicated_from_idstring (uuid) · uuidyesquote
template_idstring (uuid) · uuidyesquote_template

Primary key: id

Checks:

  • CHECK (((responded_via IS NULL) OR (responded_via = ANY (ARRAY['link'::text, 'manual'::text]))))
  • CHECK ((length(TRIM(BOTH FROM title)) > 0))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
quotes.acceptedThe customer accepted a quote.id, number, customerId, leadId, ownerUserId, versionNo, totalAmount, currency, description, lines, via
quotes.createdA quote was started.id, number, customerId, leadId, ownerUserId, totalAmount, currency
quotes.duplicatedA quote was copied into a new draft.id, fromId, ownerUserId
quotes.expiredA sent quote lapsed unanswered.id, number, ownerUserId
quotes.note_addedA note was added to a quote.id
quotes.rejectedThe customer declined a quote.id, number, customerId, ownerUserId, reason, via
quotes.revisedA quote was revised into a new version.id, versionNo
quotes.send_requestedA quote was sent by email; notifications delivers it.id, eventId, to
quotes.sentA quote went out to the customer.id, number, customerId, ownerUserId, versionNo
quotes.template_changedA quote template was saved, edited or deleted.id
quotes.updatedA draft quote was edited.id
quotes.withdrawnA quote was withdrawn.id, ownerUserId
Was this page helpful?