> ## Documentation Index
> Fetch the complete documentation index at: https://docs.invopop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Lookup guide

> Validate tax IDs against official registers in your workflows, and correct parties with the registered data.

export const WorkflowDiagram = ({workflow}) => {
  const stateColors = {
    processing: "yellow",
    sent: "blue",
    received: "blue",
    registered: "green",
    completed: "green",
    error: "red"
  };
  const StateChip = ({state, label}) => <Badge size="sm" color={stateColors[state] || "gray"} icon="square-small" iconType="solid">
      {label.charAt(0).toUpperCase() + label.slice(1)}
    </Badge>;
  const providerIcons = {
    "silo.state": "https://silo.invopop.com/images/status.svg",
    "silo.close": "https://silo.invopop.com/images/check-badge.svg",
    "silo.if": "https://assets.invopop.com/apps/silo/if.svg",
    "silo.folder": "https://silo.invopop.com/images/folder.svg",
    "silo.modify": "https://silo.invopop.com/images/modify.svg",
    "silo.correct": "https://silo.invopop.com/images/replace.svg",
    "silo.sleep": "https://assets.invopop.com/icons/sleep.svg",
    silo: "https://assets.invopop.com/apps/silo/icon.svg",
    "sequence.enumerate": "https://sequence.invopop.com/images/enumerate.svg",
    "transform.job.create": "https://transform.invopop.com/images/jobs.svg",
    webhook: "https://webhook.invopop.com/icon.svg",
    lookup: "magnifying-glass",
    dropbox: "https://dropbox.invopop.com/icon.png",
    pdf: "https://pdf.invopop.com/file-pdf.svg",
    peppol: "https://assets.invopop.com/apps/peppol/icon.svg",
    ubl: "https://assets.invopop.com/apps/ubl/logo.svg",
    cii: "https://assets.invopop.com/apps/cii/logo.svg",
    "gov-dk": "https://assets.invopop.com/flags/dk.svg",
    "gov-fi": "https://assets.invopop.com/flags/fi.svg",
    "gov-fr": "https://assets.invopop.com/flags/fr.svg",
    "chorus-pro": "https://assets.invopop.com/apps/chroruspro/icon.svg",
    "gov-es": "https://assets.invopop.com/apps/gov-es/icon.svg",
    "gov-es.sii": "https://assets.invopop.com/apps/sii/icon.svg",
    "gov-es.ticketbai": "https://assets.invopop.com/apps/ticketbai/icon.svg",
    "gov-es.facturae": "https://assets.invopop.com/apps/facturae/icon.svg",
    verifactu: "https://assets.invopop.com/apps/verifactu/icon.svg",
    "gov-pl": "https://assets.invopop.com/apps/ksef/icon.svg",
    "gov-sa": "https://assets.invopop.com/apps/zatca/icon.svg",
    "gov-ar": "https://assets.invopop.com/apps/arca/icon.svg",
    "at-pt": "https://assets.invopop.com/apps/at-pt/icon.svg",
    "sat-mx": "https://assets.invopop.com/apps/sat-mexico/icon.svg",
    "sw-sapien": "https://assets.invopop.com/apps/sw-sapien/icon.svg",
    "sdi-it": "https://assets.invopop.com/apps/sdi-italy/icon.svg",
    "ticket-it": "https://assets.invopop.com/apps/agenzia-entrate/icon.svg",
    "nfe-br": "https://assets.invopop.com/apps/notas-fiscais-eletronicas-brazil/icon.svg",
    chargebee: "https://assets.invopop.com/apps/chargebee/icon.svg",
    stripe: "https://assets.invopop.com/apps/stripe/icon.svg",
    email: "https://assets.invopop.com/apps/email/icon.svg",
    cron: "https://assets.invopop.com/apps/cron/icon.svg",
    tables: "https://assets.invopop.com/apps/tables/icon.svg",
    ilyda: "https://assets.invopop.com/apps/ilyda/icon.svg",
    invoicexpress: "https://assets.invopop.com/apps/invoicexpress/icon.svg",
    plemsi: "https://assets.invopop.com/flags/co.svg"
  };
  const iconFor = provider => {
    const parts = (provider || "").split(".");
    for (let i = parts.length; i > 0; i--) {
      const url = providerIcons[parts.slice(0, i).join(".")];
      if (url) return url;
    }
    return null;
  };
  const StepIcon = ({provider}) => {
    const url = iconFor(provider);
    return <span title={provider} className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-gray-950/10 bg-white dark:border-white/10 dark:bg-white/5">
        {url && url.startsWith("http") ? <img src={url} alt="" className="h-4 w-4" /> : null}
        {url && !url.startsWith("http") ? <Icon icon={url} iconType="solid" size={14} /> : null}
      </span>;
  };
  const renderSummary = summary => {
    const nodes = [];
    const re = /`([^`]+)`(\{[^}]*\})?/g;
    let last = 0;
    let m;
    let k = 0;
    while ((m = re.exec(summary)) !== null) {
      if (m.index > last) nodes.push(summary.slice(last, m.index));
      const attrs = m[2] || "";
      if (attrs.indexOf(".state") >= 0) {
        const state = (attrs.match(/\.state\s+\.([\w-]+)/) || [])[1] || m[1];
        nodes.push(<StateChip key={k++} state={state} label={m[1]} />);
      } else if (attrs) {
        nodes.push(<span key={k++} className="text-sm font-medium text-gray-700 dark:text-gray-300">
            {m[1]}
          </span>);
      } else {
        nodes.push(<code key={k++} className="text-sm rounded bg-gray-100 px-1 font-mono text-sm dark:bg-white/10">
            {m[1]}
          </code>);
      }
      last = m.index + m[0].length;
    }
    if (last < summary.length) nodes.push(summary.slice(last));
    return nodes;
  };
  const NoteRow = ({text}) => <div className="mt-4 mb-1 font-mono text-sm leading-5 text-gray-400 dark:text-gray-500">{"// " + text}</div>;
  const renderSteps = (steps, counter) => (steps || []).map(step => {
    counter.n += 1;
    const n = counter.n;
    const branches = (step.next || []).filter(b => b.steps && b.steps.length > 0);
    return <div key={step.id || "step-" + n}>
          {step.notes ? <NoteRow text={step.notes} /> : null}
          <div className="mt-2.5 flex items-center">
            <span className="absolute left-0 w-10 text-center font-mono text-sm text-gray-400 select-none dark:text-gray-500">
              {n}
            </span>
            <div className="flex min-w-0 flex-1 items-center gap-2 rounded-xl border border-gray-950/5 bg-white px-2 py-2 dark:border-white/10 dark:bg-gray-900">
              <StepIcon provider={step.provider} />
              <span className="text-sm shrink-0 font-medium text-gray-900 dark:text-gray-100">{step.name}</span>
              {step.summary ? <span className="text-sm min-w-0 truncate text-gray-500 dark:text-gray-400">{renderSummary(step.summary)}</span> : null}
            </div>
          </div>
          {branches.length > 0 ? <div className="ml-5 border-l border-gray-200 -my-1 py-1 pl-6 dark:border-white/10">
              {branches.map((branch, bi) => <div key={branch.code || branch.status || bi}>
                  <div className="mt-4">
                    <span className="rounded-md bg-gray-200/70 px-2 py-1 font-mono text-sm text-gray-600 dark:bg-white/10 dark:text-gray-300">
                      {branch.code || branch.status}
                    </span>
                  </div>
                  {renderSteps(branch.steps, counter)}
                </div>)}
            </div> : null}
        </div>;
  });
  const counter = {
    n: 0
  };
  const wf = workflow || ({});
  return <div className="not-prose relative my-5 rounded-2xl border border-gray-950/5 bg-gray-50 py-3 pr-4 pb-5 pl-12 dark:border-white/10 dark:bg-white/[0.03]">
      {renderSteps(wf.steps, counter)}
      {wf.rescue && wf.rescue.length > 0 ? <div className="mt-6 border-t border-dashed border-gray-300 dark:border-white/10">
          <NoteRow text="If any step fails" />
          {renderSteps(wf.rescue, counter)}
        </div> : null}
    </div>;
};

export const lookupPartyValidateWorkflow = {
  "name": "Lookup validate party",
  "description": "Validate and correct a party's tax ID against its register",
  "schema": "org/party",
  "steps": [{
    "id": "01a0f8f1-7c00-7a10-9000-000000000021",
    "name": "Validate tax ID",
    "provider": "lookup.validate",
    "summary": "Validating and correcting party, up to 30 days old",
    "config": {
      "party": "customer",
      "max_age": 30,
      "correct": true
    }
  }],
  "rescue": [{
    "id": "01a0f8f1-7c00-7a10-9000-0000000000ff",
    "name": "Set state",
    "provider": "silo.state",
    "summary": "Set state to `error`{.state .error}",
    "config": {
      "state": "error"
    }
  }]
};

export const lookupInvoiceStopNotFoundWorkflow = {
  "name": "Lookup stop on unregistered customer",
  "description": "Reject an invoice whose customer tax ID is not on the register, sign the rest",
  "schema": "bill/invoice",
  "steps": [{
    "id": "01a0f8f1-7c00-7a10-9000-000000000011",
    "name": "Validate tax ID",
    "provider": "lookup.validate",
    "summary": "Validating and correcting customer, up to 30 days old",
    "config": {
      "party": "customer",
      "max_age": 30,
      "correct": true
    },
    "next": [{
      "status": "KO",
      "code": "not-found",
      "stop": true,
      "steps": [{
        "id": "01a0f8f1-7c00-7a10-9000-000000000012",
        "name": "Set state",
        "provider": "silo.state",
        "summary": "Set state to `rejected`{.state .rejected}",
        "config": {
          "state": "rejected"
        }
      }]
    }]
  }, {
    "id": "01a0f8f1-7c00-7a10-9000-000000000013",
    "name": "Sign envelope",
    "provider": "silo.close"
  }],
  "rescue": [{
    "id": "01a0f8f1-7c00-7a10-9000-0000000000ff",
    "name": "Set state",
    "provider": "silo.state",
    "summary": "Set state to `error`{.state .error}",
    "config": {
      "state": "error"
    }
  }]
};

export const lookupInvoiceValidateCustomerWorkflow = {
  "name": "Lookup validate invoice customer",
  "description": "Validate and correct the customer's tax ID, then sign the invoice",
  "schema": "bill/invoice",
  "steps": [{
    "id": "01a0f8f1-7c00-7a10-9000-000000000001",
    "name": "Validate tax ID",
    "provider": "lookup.validate",
    "summary": "Validating and correcting customer, up to 30 days old",
    "config": {
      "party": "customer",
      "max_age": 30,
      "correct": true
    }
  }, {
    "id": "01a0f8f1-7c00-7a10-9000-000000000002",
    "name": "Sign envelope",
    "provider": "silo.close"
  }],
  "rescue": [{
    "id": "01a0f8f1-7c00-7a10-9000-0000000000ff",
    "name": "Set state",
    "provider": "silo.state",
    "summary": "Set state to `error`{.state .error}",
    "config": {
      "state": "error"
    }
  }]
};

Invopop's [Lookup app](/apps/lookup) checks the tax ID of a party against the official register that issues it. Add its **Validate Tax IDs** step to an invoice or party workflow to make sure that the tax ID exists before you sign, send or register a document, and to write the party's legal name into the document.

Lookup keeps recent register answers in a cache, so repeated checks of the same tax ID are faster and do not call the register again. The step decides how old a cached answer may be, or asks the register every time.

## How it works

Each **Validate Tax IDs** step checks one party:

* In an `invoice` workflow, the step checks the customer or the supplier, as configured.
* In a `party` workflow, the step checks the party document itself.

The step checks the party's `tax_id`. Entries in `identities` are not checked. A tax ID that no register covers is skipped and costs nothing.

## Set up the step

<Steps>
  <Step title="Enable the Lookup app">
    In the Console, open the apps directory by clicking the icon next to **Apps** in the sidebar. Find **Lookup** in the list of available apps and enable it in your workspace.
  </Step>

  <Step title="Add the step to a workflow">
    Open an `invoice` or `party` [workflow](/guides/workflows) and add the **Validate Tax IDs** step. Place it before the steps that need a valid tax ID, such as **Sign envelope**, or a step that sends the invoice to a tax authority or network.
  </Step>

  <Step title="Configure the step">
    Choose the party to check, how old a cached answer may be, and whether the step corrects the party. See [Step configuration](#step-configuration) below.
  </Step>
</Steps>

<Prompt description="Ask your agent to add tax ID validation to one of your workflows." icon="badge-check" actions={["copy", "cursor"]}>
  Read [https://docs.invopop.com/guides/lookup.md](https://docs.invopop.com/guides/lookup.md) and [https://docs.invopop.com/guides/workflows.md](https://docs.invopop.com/guides/workflows.md). In my sandbox workspace, using the token in INVOPOP\_TOKEN, check that the Lookup app is enabled. Then take the invoice workflow named WORKFLOW\_NAME and propose where to add the Validate Tax IDs step, which party it should check, the max\_age and correct settings it should use, and how the workflow should handle the not-found, not-supported and found-name-mismatch codes. Show me the workflow JSON before you change anything, and do not invent result codes that the guide does not list.
</Prompt>

## Step configuration

| Field | Values | Default | What it does |
| - | - | - | - |
| **Invoice party to check** | Customer, Supplier | Customer | The invoice party to check. A `party` workflow always checks the party document. |
| **Reuse a previous check for (days)** | 0 to 3650 | 30 | The oldest cached answer that the step accepts. `0` asks the register every time. |
| **Correct the party** | On, Off | On | Write the register's data into the document when the check passes. |

In the workflow JSON, these fields are `party`, `max_age` and `correct`:

```json theme={"system"}
{
  "party": "customer",
  "max_age": 30,
  "correct": true
}
```

## Handle the results

The step returns a status and a code. After `OK` or `SKIP`, the workflow continues with the next step. After `KO`, the workflow stops and runs its rescue steps, unless an [exception](/guides/workflows#creating-conditions-and-exceptions) handles that code. Add conditions and exceptions only for the codes that your process must handle in a special way.

### The tax ID is valid

The step returns `OK`:

* **`found`**: the register confirms the tax ID, and the names match. The comparison ignores case, punctuation and extra spaces, so `Acme Trading GmbH.` and `ACME TRADING GMBH` match.
* **`found-name-mismatch`**: the register confirms the tax ID, but the name in the document is different. The message gives the registered name. With **Correct the party** on, the step writes that name into the document. To review these documents instead, turn the correction off and add a condition on this code.

### The tax ID is not valid

The step returns `KO`:

* **`not-found`**: the register does not hold the tax ID. Check the tax ID with your customer. To reject these invoices automatically, see [Stop on an unregistered customer](#stop-on-an-unregistered-customer).
* **`input`**: the register rejects the tax ID as badly formed, for example a code with the wrong length. Correct the tax ID in the document and run the job again.

### The step is skipped

The step returns `SKIP`, and the workflow continues:

* **`no-identifiers`**: the party has no tax ID, as is common for a consumer. To handle these documents differently, add a condition with the status **Any** on this code.
* **`missing-party`**: the invoice has no party to check, such as a simplified invoice without a customer.
* **`not-supported`**: no register covers the tax ID.

### The register does not answer

The step returns `ERR` with the code **`upstream`**, and Invopop retries it automatically. You do not need to do anything. The step never uses an older cached answer in place of the register's answer.

### The step cannot run

The step returns `KO` when its setup is wrong:

* **`not-enrolled`**: the Lookup app is not enabled in the workspace. Enable it in the Console.
* **`not-configured`**: the app is not set up correctly. Contact [support](mailto:support@invopop.com).
* **`silo-entry`**: the document of the job cannot be found.
* **`bad-document`**: the document cannot be read.
* **`bad-config`**: the step configuration cannot be read. Open the step and save its configuration again.
* **`invalid-party`**: the step selects an unknown party. Select the customer or the supplier.
* **`unsupported-schema`**: the document is not an invoice or a party. Use the step in an `invoice` or `party` workflow.

## Correction

With **Correct the party** on, a check that passes writes the register's data into the document:

* **Name:** the registered name replaces the document's name whenever the two differ, including a difference in case only. The registered spelling is the legal one.
* **Address:** a structured address from the register becomes the party's first address, and the other addresses stay.
* **Tax ID:** never changed.

Nothing is written when the check does not pass, when the register gives no name, or when the document is already signed. To correct a document and then sign it, place **Validate Tax IDs** before **Sign envelope**.

The correction never changes the result code or the message. A corrected document still returns `found` or `found-name-mismatch`.

## Workflow examples

### Validate the invoice customer

Validate the customer's tax ID, write the registered name into the invoice, and sign it.

<Tabs>
  <Tab title="Workflow">
    <WorkflowDiagram workflow={lookupInvoiceValidateCustomerWorkflow} />
  </Tab>

  <Tab title="Code">
    Copy and paste into a new [Empty Invoice workflow](https://console.invopop.com/redirect/workflows/new?template=empty-invoice) code view.

    ```json Lookup validate invoice customer workflow theme={"system"}
    {
        "name": "Lookup validate invoice customer",
        "description": "Validate and correct the customer's tax ID, then sign the invoice",
        "schema": "bill/invoice",
        "steps": [
            {
                "id": "01a0f8f1-7c00-7a10-9000-000000000001",
                "name": "Validate tax ID",
                "provider": "lookup.validate",
                "summary": "Validating and correcting customer, up to 30 days old",
                "config": {
                    "party": "customer",
                    "max_age": 30,
                    "correct": true
                }
            },
            {
                "id": "01a0f8f1-7c00-7a10-9000-000000000002",
                "name": "Sign envelope",
                "provider": "silo.close"
            }
        ],
        "rescue": [
            {
                "id": "01a0f8f1-7c00-7a10-9000-0000000000ff",
                "name": "Set state",
                "provider": "silo.state",
                "summary": "Set state to `error`{.state .error}",
                "config": {
                    "state": "error"
                }
            }
        ]
    }
    ```
  </Tab>
</Tabs>

### Stop on an unregistered customer

An exception on the `not-found` code sets the state to `rejected` and stops the job, so the invoice is never signed. Every other invoice is signed as usual.

<Tabs>
  <Tab title="Workflow">
    <WorkflowDiagram workflow={lookupInvoiceStopNotFoundWorkflow} />
  </Tab>

  <Tab title="Code">
    Copy and paste into a new [Empty Invoice workflow](https://console.invopop.com/redirect/workflows/new?template=empty-invoice) code view.

    ```json Lookup stop on unregistered customer workflow theme={"system"}
    {
        "name": "Lookup stop on unregistered customer",
        "description": "Reject an invoice whose customer tax ID is not on the register, sign the rest",
        "schema": "bill/invoice",
        "steps": [
            {
                "id": "01a0f8f1-7c00-7a10-9000-000000000011",
                "name": "Validate tax ID",
                "provider": "lookup.validate",
                "summary": "Validating and correcting customer, up to 30 days old",
                "config": {
                    "party": "customer",
                    "max_age": 30,
                    "correct": true
                },
                "next": [
                    {
                        "status": "KO",
                        "code": "not-found",
                        "stop": true,
                        "steps": [
                            {
                                "id": "01a0f8f1-7c00-7a10-9000-000000000012",
                                "name": "Set state",
                                "provider": "silo.state",
                                "summary": "Set state to `rejected`{.state .rejected}",
                                "config": {
                                    "state": "rejected"
                                }
                            }
                        ]
                    }
                ]
            },
            {
                "id": "01a0f8f1-7c00-7a10-9000-000000000013",
                "name": "Sign envelope",
                "provider": "silo.close"
            }
        ],
        "rescue": [
            {
                "id": "01a0f8f1-7c00-7a10-9000-0000000000ff",
                "name": "Set state",
                "provider": "silo.state",
                "summary": "Set state to `error`{.state .error}",
                "config": {
                    "state": "error"
                }
            }
        ]
    }
    ```
  </Tab>
</Tabs>

### Validate a party

Validate a party's tax ID and write the registered name into the party, for example before you register it with a tax authority.

<Tabs>
  <Tab title="Workflow">
    <WorkflowDiagram workflow={lookupPartyValidateWorkflow} />
  </Tab>

  <Tab title="Code">
    ```json Lookup validate party workflow theme={"system"}
    {
        "name": "Lookup validate party",
        "description": "Validate and correct a party's tax ID against its register",
        "schema": "org/party",
        "steps": [
            {
                "id": "01a0f8f1-7c00-7a10-9000-000000000021",
                "name": "Validate tax ID",
                "provider": "lookup.validate",
                "summary": "Validating and correcting party, up to 30 days old",
                "config": {
                    "party": "customer",
                    "max_age": 30,
                    "correct": true
                }
            }
        ],
        "rescue": [
            {
                "id": "01a0f8f1-7c00-7a10-9000-0000000000ff",
                "name": "Set state",
                "provider": "silo.state",
                "summary": "Set state to `error`{.state .error}",
                "config": {
                    "state": "error"
                }
            }
        ]
    }
    ```
  </Tab>
</Tabs>

***

<Card title="Participate in our community" icon="forumbee" href="https://community.invopop.com" arrow="true" horizontal>
  Ask and answer questions about the Lookup app →
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.