> ## 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.

# Migrate from API v1 to v2

> Integration guide for Migrate from API v1 to v2

Move one business workflow at a time, beginning with reads and test-mode records.
Keep the old integration available until the new one produces the expected result.
Changing the base URL alone is not enough: v2 has different resource routes, payload
fields, pagination and operation-specific side effects.

This guide describes the current v2 contract. Compare it with the v1 requests your
application actually sends; v1 integrations do not all have the same wrappers or
response assumptions. The [archived v1 reference](https://documenter.getpostman.com/view/21022234/UyxnFQzk)
is historical context, not a contract for v2.

## 1. Inventory the existing integration

For each workflow, record the method and path, credential environment, owning team,
request shape, saved identifiers, response checks, retry behavior and downstream
effects. Include background jobs and webhook consumers, not just interactive screens.

| Existing task | v2 entry point | Migration check |
| - | - | - |
| Customer records | `/clients` | Save `data.id`; choose explicit duplicate lookup behavior |
| Products or services | `/services` | Map descriptions, quantity, unit price and tax inputs using the item schema |
| Income invoices | `/invoices/income` | Create with the published body; retain UUID and business idempotency key |
| Review before issuance | `/invoices/draft` | Saving and previewing do not issue; updating requires the complete intended body |
| Record money received | `/payments/register` | Records an existing payment; does not charge a card |
| Request payment | `/payments/request` | A checkout URL is not proof of settlement |
| Refund | `/payments/{id}/refund` | Recording an external refund and requesting a Stripe refund are different choices |
| Self-invoicing receipt | `/receipts` | `pending` describes invoicing state, not bank settlement |
| Multiple issuing businesses | Documented `team` parameter | Verify Connect eligibility; never assume every route supports overrides |
| Event processing | `/webhooks` | Support the actual payload version and authentication family |

Use the [API reference](/reference/listClients) for exact methods, bodies and responses.
An operation with an availability warning is not accessible through the public gateway,
even if a backend handler exists.

## 2. Set the environment and credential

The public v2 base URL is `https://api.gigstack.io/v2` for both standard live and test
API keys. A test key selects test data; it is not an internal staging key. Internal
staging is a different deployment with its own URL and credentials. See
[authentication and environments](/authentication).

Create a test key in [API settings](https://app.gigstack.pro/settings?tab=api), then
load it locally in Bash rather than embedding it in source code:

```bash theme={null}
export GIGSTACK_BASE_URL='https://api.gigstack.io/v2'
read -rsp 'gigstack test API key: ' GIGSTACK_API_KEY; echo
export GIGSTACK_API_KEY

curl --fail-with-body --get "$GIGSTACK_BASE_URL/clients" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  --data-urlencode 'limit=2' > migration-clients.json

jq '{count: (.data | length), has_more, next}' migration-clients.json
```

Expect HTTP `200` and a `data` array. Empty test data is valid. `curl` exits `22` on
an HTTP error with `--fail-with-body`; inspect the saved response before proceeding.
Do not send keys from browser code. Account signup uses a separate partner credential,
and public health routes are separate from authenticated resource calls.

## 3. Update response and error handling

Most responses have `success`, `data` and a millisecond `timestamp`, but this is not
universal. Authentication failures, fiscal issuance and some SAT endpoints use other
shapes. Check the HTTP status first and then parse the operation's documented body.
A successful status code alone may acknowledge asynchronous work.

| Operation | Completion check |
| - | - |
| Client creation | Save `data.id`; inspect fiscal validation separately when relevant |
| Draft creation | `data.status: "draft"`; no CFDI has been issued |
| Mexican draft stamp | Save `data.uuid`, then retrieve the issued invoice; the former draft ID may no longer resolve |
| Income batch | Poll until processing ends, then inspect `result` and every item's final status |
| PPD-linked payment | Check both the payment and the parent invoice for the completed complement and balance |
| Invoice cancellation | Read the stored invoice status after the provider response; acknowledgment can still mean pending |
| Refund | Reconcile the recorded refund and, when requested, the processor's result |

Do not turn every `400` into “fix your input.” A repeated payment or receipt creation
key can return `400 resource_conflict` for an already-existing record. See
[responses and retries](/responses), [troubleshooting](/troubleshooting), and the
resource's error-handling section.

## 4. Replace pagination loops

For ordinary cursor lists, send the previous response's `next` back as `next`, keeping
all other filters. Stop when `has_more` is false. Metadata-filtered lists and search
can use page-based pagination; SAT invoices and nested batch/document responses have
other names. Do not reuse a cursor loop blindly across resources.

The following is a complete **read-only customer-list example** for Node.js with
built-in `fetch`. Save it as `list-clients.mjs`, with the environment variables from
step 2 already exported. It counts records rather than printing customer data.

```javascript theme={null}
const base = process.env.GIGSTACK_BASE_URL
const key = process.env.GIGSTACK_API_KEY
if (!base || !key) throw new Error('Set GIGSTACK_BASE_URL and GIGSTACK_API_KEY')

async function* listClients() {
    let next
    const cursors = new Set()
    // This limit is a local guard, not an API limit. Raise it deliberately if needed.
    for (let page = 0; page < 10000; page++) {
        const url = new URL(`${base.replace(/\/$/, '')}/clients`)
        url.searchParams.set('limit', '100')
        if (next) url.searchParams.set('next', next)
        const response = await fetch(url, {
            headers: { Authorization: `Bearer ${key}` },
            signal: AbortSignal.timeout(30000),
        })
        if (!response.ok) throw new Error(`Customer list returned HTTP ${response.status}`)
        const body = await response.json()
        if (!Array.isArray(body.data) || typeof body.has_more !== 'boolean') {
            throw new Error('Unexpected customer list response')
        }
        for (const client of body.data) yield client
        if (!body.has_more) return
        if (typeof body.next !== 'string' || !body.next || cursors.has(body.next)) {
            throw new Error('Missing or repeated pagination cursor')
        }
        cursors.add(body.next)
        next = body.next
    }
    throw new Error('Stopped at the local pagination guard; export is incomplete')
}

let count = 0
for await (const client of listClients()) count++
console.log(`Read ${count} customers`)
```

Run `node list-clients.mjs`. Exit code `0` means the loop reached its final page;
a nonzero exit means the export is incomplete. The example intentionally adds no
metadata filter. For one, use the endpoint's page-based response and `page` parameter
instead. A live listing is not a transactional snapshot: reconcile changes that occur
during a long export.

## 5. Migrate one complete test workflow

Use the [customer recipe](/recipes/customer), then select the issuing country:
[Mexico](/countries/mexico) or [Colombia](/countries/colombia). The team's invoicing
configuration selects the provider; the recipient's `address.country` does not.

For a Mexican issuer, run these steps with your own test-mode records:

1. Create or find a customer using a stable reference from your application. Save
   the returned ID; resolve ambiguous matches rather than picking the first result.
2. Supply valid fiscal data before issuance. Customer creation alone is not fiscal
   validation. The recipe includes a separate Mexican test fixture.
3. Follow [draft, preview, and issue](/recipes/invoice). Keep the original request
   body so a later draft update can send all intended fields.
4. If money was received, follow [payment registration](/recipes/payment). Choose
   invoice automation deliberately to avoid issuing the sale twice.
5. If the invoice is paid later, follow [the PPD recipe](/recipes/paid-later) and check
   the complement and remaining balance, not only the payment's creation status.
6. Exercise your parser with redacted success/error fixtures and your retry logic
   with timeout simulations before enabling real writes.

The Colombia guide documents current public-schema compatibility and limitations.
The Mexican recipe verification is not evidence of a completed Colombian flow.

### Side effects to preserve or disable deliberately

| Action | Check before migrating |
| - | - |
| Update a client | `check_pending_receipts` defaults to true and can invoice pending receipts after fiscal validation; set false when that is not intended |
| Update a draft | Omitted items/defaulted fields may be reset; use the full intended body |
| Register a payment | `automation_type` and `ppd_invoice_id` can request fiscal work; `none` does not disable the explicit PPD link's complement handling |
| Send an invoice or receipt | Set delivery fields explicitly for the operation; avoid emailing customers during tests |
| Stamp a draft | Verify the draft's stored test mode, not just the calling key |
| Refund or cancel | Fiscal cancellation, a credit note and returning money are separate operations |

## 6. Move webhooks and retries

Build the new webhook receiver before redirecting production traffic. Read
[webhook payload versions and delivery behavior](/guides/webhooks): resource events
are unsigned, while SAT-sync and batch-completion events can carry signatures and
are sent only once. A secret returned by webhook creation does not sign every event.

Persist verified notifications before acknowledgment and use a durable worker. V2
has a stable delivery `id`; v1 has no unique delivery ID, so `event + data.id` is not
a safe permanent deduplication key for repeated updates. Reconcile current resource
state and protect your own side effects with business identifiers.

For API retries, keep an operation's existing idempotency key for the same business
event. Do not invent a universal `Idempotency-Key` header: batch creation and some
other operations use that header; payment/invoice creation can use a body field;
refund and cancellation have no documented equivalent. After an ambiguous write,
read and reconcile before repeating it.

## 7. Cut over and verify

First compare read results between your application and the v2 integration using
known identifiers and amounts. Then enable one workflow for a small, deliberate
scope. Monitor failures, duplicate business references, pending asynchronous work
and webhook inbox age. Keep the previous application version deployable.

A rollback of your application does not undo a payment, issued fiscal document or
refund. Preserve the IDs and completed results from the cutover period so the old
integration does not submit those business events again. Do not run both writers
for the same sale while comparing them.

Before declaring the migration complete, verify:

* Correct environment, team and mode on returned records.
* Full pagination without silently stopping at the first page.
* Exact amount units, taxes and resulting totals for your workflows.
* Successful asynchronous results, not merely accepted requests.
* Duplicate and timeout handling for each write operation.
* Webhook authentication, durable intake, worker recovery and missed-event reconciliation.
* Customer delivery settings and every intended automation.

## v1 retirement and support

This guide does not publish a confirmed v1 retirement date. Ask gigstack for the
current policy that applies to your integration before scheduling a mandatory
cutover; placeholder dates are not a deprecation notice.

For help, contact [support@gigstack.io](mailto:support@gigstack.io) with the method, path, environment, time,
HTTP status and a redacted error body. Include a resource or process reference when
appropriate, but never API keys, signing secrets, certificates or signed download URLs.
See [verification coverage](/verification) for what the published recipes actually tested.


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