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

# Draft, preview, and issue an invoice

> Turn one consulting sale into a CFDI, with a review step before stamping.

<Note>**Mexico example.** This walkthrough uses the Mexican CFDI flow. For another
issuing country, start with [shared fields and country rules](/concepts/shared-fields).</Note>

**Case:** a Mexican customer paid MXN 1,160 by bank transfer for one hour of consulting.
You need an income CFDI (`I`) with MXN 1,000 subtotal and MXN 160 IVA.

Use a **test key** for your first run. Complete [the customer recipe](/recipes/customer)
and save its `CLIENT_ID`. For stamping, the customer needs valid fiscal data and the
issuing team needs its invoicing provider and CSD configured. Complete
[issuer setup](/concepts/issuer-setup#mexico) first. A contact-only customer
is enough to save a draft, but not to complete the fiscal flow. For a synthetic run, use
the [test fiscal customer](/recipes/customer#test-fiscal-customer).

<Note>
  Before creating a test draft, confirm the team’s notification routing with the account
  owner. `send_email: false`, `ignore_emails: true` and `automation_type: "none"` do not
  suppress every account notification workflow. Invoice creation can attempt an account
  marketing event even in test mode; these flags are not a guarantee of a notification-free
  run. Use an approved sandbox with known recipients. This is a source-reviewed behavior;
  the recipe tests did not verify marketing delivery.
</Note>

## 1. Save the request as a file

This is a paid-in-full example: `PUE` describes the payment timing; `03` means bank transfer.
Use the customer's actual `use`, and the product and taxes appropriate to your sale.

```bash theme={null}
set -euo pipefail
jq -n --arg client "$CLIENT_ID" '{
  invoice_type: "I",
  client: {id: $client},
  currency: "MXN",
  exchange_rate: 1,
  use: "G03",
  payment_method: "PUE",
  payment_form: "03",
  items: [{
    description: "One hour of consulting",
    product_key: "80101500",
    unit_key: "E48",
    quantity: 1,
    unit_price: 1000,
    taxes: [{type: "IVA", rate: 0.16, inclusive: false, withholding: false, factor: "Tasa"}]
  }],
  automation_type: "none",
  send_email: false,
  ignore_emails: true,
  metadata: {external_id: "order-1042"}
}' > draft-body.json

curl --fail-with-body "$GIGSTACK_BASE_URL/invoices/draft" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' \
  --data @draft-body.json > draft.json

DRAFT_ID=$(jq -er '.data.id' draft.json)
export DRAFT_ID
jq '{id: .data.id, status: .data.status, livemode: .data.livemode}' draft.json
```

**Expected:** HTTP `201`, `data.status: "draft"`, a draft ID, and `livemode: false`
with a test key. Saving the draft has not issued a CFDI.

## 2. Preview and review

```bash theme={null}
curl --fail-with-body -X POST \
  "$GIGSTACK_BASE_URL/invoices/draft/$DRAFT_ID/preview" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' --data '{}' > preview.json
```

**Expected:** HTTP `200` and `message: "Preview PDF generated"`. Inspect the returned
recipient, items, currency, and taxes. The tested response contains a base64 PDF at
`data.pdf`. Save it locally:

```bash theme={null}
jq -er '.data.pdf' preview.json | base64 --decode > preview.pdf
```

Open `preview.pdf` to review it. A preview is not a stamped invoice.

<Warning>
  When updating a draft, send the complete intended draft body, including `items`.
  A notes-only `PUT` cleared the items in our staging test. Several omitted fields also
  receive defaults, so do not treat this operation as a partial PATCH. Read the draft back
  and compare it before stamping.
</Warning>

## 3. Issue the reviewed draft

Run this step only for the draft you intend to issue. Verify that the draft itself has
`livemode: false` during testing: a test key alone does not change the stored draft's mode.

```bash theme={null}
curl --fail-with-body -X POST \
  "$GIGSTACK_BASE_URL/invoices/draft/$DRAFT_ID/stamp" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"send_email":false,"ignore_emails":true}' > invoice.json

INVOICE_UUID=$(jq -er '.data.uuid' invoice.json)
export INVOICE_UUID
jq '{uuid: .data.uuid, status: .data.status, livemode: .data.livemode,
     subtotal: .data.subtotal, taxes: .data.taxes, total: .data.total}' invoice.json
```

**Observed test result:** HTTP **`200`**, `status: "valid"`, `livemode: false`, subtotal
`1000`, taxes `160`, total `1160`, and a UUID. The response did not contain `data.id`.
Keep the UUID; fetching the issued invoice with the former draft ID returned `404`.

## 4. Fetch the invoice and its files

```bash theme={null}
curl --fail-with-body \
  "$GIGSTACK_BASE_URL/invoices/income/$INVOICE_UUID" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY"

curl --fail-with-body \
  "$GIGSTACK_BASE_URL/invoices/$INVOICE_UUID/files" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY"
```

Both returned `200` in the test flow. The files response contains a `data` array; entries
have `filename`, `type`, and `content`. See the [files contract](/reference/getInvoicesByIdFiles)
for encoding and options. Keep file contents and any signed download URLs private.

If stamping times out, [reconcile the result before retrying](/troubleshooting).
If you only need to discard an unissued draft, use
[Delete a draft](/reference/deleteInvoicesDraftById); cancelling an issued CFDI is a different operation.


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