# Build with agents
Source: https://docs.gigstack.io/agents
Give coding agents the same reference you use, with explicit control over writes.
## Give your agent the docs
* Start with `/llms.txt`, the documentation index.
* Use `/llms-full.txt` for the full documentation context.
* Read [`openapi.json`](/openapi.json) for operation IDs, parameters, schemas, and examples.
* Add `.md` to a guide URL for its Markdown version, such as [`quickstart.md`](/quickstart.md).
* Use the page menu to copy a page or connect the documentation search MCP.
The documentation MCP searches these docs. It does not grant access to your gigstack account.
## A starting prompt
```text theme={null}
Build an integration with the gigstack API.
Read the docs index and OpenAPI reference at the documentation site's origin.
Start with GET /clients using a test API key from my local environment.
Keep the key out of generated code and logs.
Check the exact endpoint contract; do not invent fields, filters, or idempotency support.
Before writing, identify the team, key mode, and requested operation.
Ask me to approve issuing or cancelling invoices, collecting or refunding payments,
changing SAT credentials, or deleting records unless I already authorized that action.
If a write times out, look for its result before retrying.
Report the HTTP status and returned resource ID as evidence of completion.
```
## Review what the agent is about to do
| Task | Useful review |
| - | - |
| Read customers | Team, mode, filters, and pagination |
| Prepare an invoice | Recipient RFC, items, currency, taxes, and payment method |
| Issue a CFDI | Approved draft and the intended issuing team |
| Record or refund a payment | Amount, currency, reference, and duplicate check |
| Retry a failed operation | Whether the first attempt already created a resource |
A documentation example is a starting point for a request. It is not authorization to execute it.
# Headless signup (API-only path)
Source: https://docs.gigstack.io/api-reference/auth/headless-signup-api-only-path
/openapi.json post /auth/signup
Create a complete gigstack account end-to-end without the alphav2 web UI.
Designed for AI agents and partner integrations: a single call provisions
a Firebase Auth user, billing account, team, plan subscription, and returns
live + test API keys.
**Auth:** This endpoint authenticates with `X-Internal-API-Key` (partner-issued),
NOT a normal `Authorization: Bearer` API token. Bearer tokens are scoped to a
team — a brand-new caller has none yet.
**Idempotency:** `Idempotency-Key` header (UUID v4) is required. Retries with
the same key return the cached response and never re-create resources.
**Plan paths:**
- `plan_id: "free"` — direct Firestore write, no Stripe, instant activation.
- Any paid plan — uses `stripe.subscriptions.create` with the supplied
PaymentMethod token (off-session). No Checkout redirect. `billingAccount.haveAPIAccess`
is set synchronously so the API gate passes on the first call.
**RFC and FIEL:** not required at signup. Defaults to RFC genérico
(`XAXX010101000`). Customers can later upload their own via
`POST /v2/teams/{team_id}/sat-connection`. Clients, payments, services,
receipts, and webhooks all work without FIEL.
**API keys:** returned in the response body **once** — not retrievable later.
The caller must store them immediately.
# Health check
Source: https://docs.gigstack.io/api-reference/auth/health-check
/openapi.json get /auth/health
Liveness probe for the auth module. Unauthenticated — the whole auth router is mounted
as a public endpoint, so no credential is required or inspected.
# Health check
Source: https://docs.gigstack.io/api-reference/catalogs/health-check
/openapi.json get /catalogs/health
Liveness probe for the catalogs module. Unauthenticated.
# Search SAT product keys
Source: https://docs.gigstack.io/api-reference/catalogs/search-sat-product-keys
/openapi.json get /catalogs/product-keys
Full-text search over the SAT `c_ClaveProdServ` catalog, the same catalog the dashboard
searches when you pick a default product key. Returns the codes you assign to an item's
`product_key`.
**Search Capabilities:**
- Matches on code, description and SAT taxonomy (type, division, group)
- Typo-tolerant fuzzy matching, ranked by relevance
- Paginated results
**Notes:**
- The catalog is published by the SAT and is identical for every team, so results are not
affected by `livemode` and contain no team data. Authentication is still required.
- Typesense must be configured for your team.
- The `q` (or `query`) parameter is required.
# Search SAT unit keys
Source: https://docs.gigstack.io/api-reference/catalogs/search-sat-unit-keys
/openapi.json get /catalogs/unit-keys
Full-text search over the SAT `c_ClaveUnidad` catalog. Returns the codes you assign to an
item's `unit_key`, together with the name commonly stored as `unit_name`.
**Search Capabilities:**
- Matches on unit key and name
- Typo-tolerant fuzzy matching, ranked by relevance
- Paginated results
**Notes:**
- The catalog is published by the SAT and is identical for every team, so results are not
affected by `livemode` and contain no team data. Authentication is still required.
- Typesense must be configured for your team.
- The `q` (or `query`) parameter is required.
# Create client
Source: https://docs.gigstack.io/api-reference/clients/create-client
/openapi.json post /clients
Create a new client with fiscal information for Mexican tax compliance.
**Duplicate Prevention (Upsert):** Use the `search` parameter to find existing clients before creating:
- If a match is found and `search.update` is `false` (default): Returns the existing client without modifications.
- If a match is found and `search.update` is `true`: Updates the existing client with the provided data and returns it.
- If no match is found: Creates a new client.
This is useful for integrations that may send the same client multiple times.
**gigstack Connect:** Create clients for other teams using the `team` parameter.
# Delete client
Source: https://docs.gigstack.io/api-reference/clients/delete-client
/openapi.json delete /clients/{id}
Delete a specific client.
**gigstack Connect:** Delete other teams' clients using the `team` parameter.
# Get client
Source: https://docs.gigstack.io/api-reference/clients/get-client
/openapi.json get /clients/{id}
Retrieve a specific client by ID.
**gigstack Connect:** Access other teams' clients using the `team` parameter.
# Get client customer portal access token
Source: https://docs.gigstack.io/api-reference/clients/get-client-customer-portal-access-token
/openapi.json post /clients/customerportal
Generate a secure access token for client customer portal.
**gigstack Connect:** Access other teams' customer portal using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/api-reference/clients/health-check
/openapi.json get /clients/health
Liveness probe for the clients module. Unauthenticated.
# List clients
Source: https://docs.gigstack.io/api-reference/clients/list-clients
/openapi.json get /clients
Retrieve a paginated list of clients with powerful filtering capabilities.
**gigstack Connect:** Access other teams' clients using the `team` parameter.
**Filtering Options:**
- Filter by creation date using comparison operators
- Filter by metadata fields using dot or underscore notation (e.g., `metadata.external_id` or `metadata_external_id`)
# List support documents
Source: https://docs.gigstack.io/api-reference/clients/list-support-documents
/openapi.json get /clients/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve all supporting documents attached to a client.
**gigstack Connect:** View documents for other teams' clients using the `team` parameter.
Documents are returned sorted by creation date (newest first).
**Note:** Client documents are automatically inherited by all the client's invoices and payments.
# Search clients
Source: https://docs.gigstack.io/api-reference/clients/search-clients
/openapi.json get /clients/search
Full-text search across clients using Typesense. Provides fast, typo-tolerant search capabilities.
**gigstack Connect:** Access other teams' clients using the `team` parameter.
**Search Capabilities:**
- Search across client name, email, tax ID, legal name, and metadata
- Typo-tolerant fuzzy matching
- Paginated results
**Requirements:**
- Typesense must be configured for your team
- The `q` (or `query`) parameter is required
# Stamp pending receipts
Source: https://docs.gigstack.io/api-reference/clients/stamp-pending-receipts
/openapi.json post /clients/{id}/stamp-pending-receipts
Stamp the client's pending receipts into CFDI invoices.
**Batch cap:** at most **100** receipts are stamped per call. Receipts are processed
five at a time. If the client has more than 100 pending receipts, call the endpoint
repeatedly until `data.remaining` reaches `0`.
`data.remaining` is re-counted from Firestore *after* stamping, so it includes both
receipts beyond the 100-item cap and receipts that failed in this run (they stay
`pending`).
**Fiscal prerequisites.** Before stamping anything the handler checks that the client
has an RFC (`rfc`, falling back to `tax_id`), a legal name (`legal_name`, falling back
to `name`), `address.zip`, and `tax_system`. If any is missing it returns `400` with
`error.code: client_fiscal_data_incomplete` and stamps nothing. `email` and
`address.country` are not checked (`country` defaults to `MEX`).
**gigstack Connect:** Stamp other teams' client receipts using the `team` parameter.
# Update client
Source: https://docs.gigstack.io/api-reference/clients/update-client
/openapi.json put /clients/{id}
Update an existing client.
**Pending receipts:** By default, after a successful update, if the client passes fiscal validation, all of the client's pending receipts are automatically invoiced using the new client data. Set `check_pending_receipts: false` in the body to skip this behavior. When the check runs, the response includes a `pending_receipts` summary.
**gigstack Connect:** Update other teams' clients using the `team` parameter.
# Upload CSF PDF to create or update client
Source: https://docs.gigstack.io/api-reference/clients/upload-csf-pdf-to-create-or-update-client
/openapi.json post /clients/csf
Upload a CSF (Constancia de Situación Fiscal) PDF file from SAT to automatically extract fiscal information and create a new client or update an existing one.
**How it works:**
1. Upload the CSF PDF file as `multipart/form-data`
2. The system extracts RFC and CIF from the PDF
3. Validates the fiscal information against SAT
4. Creates a new client or updates an existing one with the fiscal data
**Query Parameters:**
- `client_id` (optional): If provided, updates the existing client. If omitted, creates a new client.
**Extracted Information:**
- Legal name (Razón Social)
- RFC (Tax ID)
- Fiscal regime (Régimen Fiscal)
- Fiscal type (company/individual)
- Fiscal status
- Complete address (street, exterior/interior number, neighborhood, city, state, zip code)
**gigstack Connect:** Create or update clients for other teams using the `team` parameter.
# Upload support document
Source: https://docs.gigstack.io/api-reference/clients/upload-support-document
/openapi.json post /clients/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Upload a supporting document (contract, proof of delivery, etc.) for a client.
**SAT 2026 Compliance:** Documents uploaded to a client are automatically inherited by all
of the client's invoices and payments, simplifying compliance management.
**gigstack Connect:** Upload documents for other teams' clients using the `team` parameter.
**Supported File Types:**
- PDF files (.pdf)
- Images (.png, .jpg, .jpeg, .webp)
**File Size Limit:** 10MB
# Validate client fiscal information
Source: https://docs.gigstack.io/api-reference/clients/validate-client-fiscal-information
/openapi.json post /clients/validate/{id}
Re-runs the full SAT validation for a client on demand. Performs three independent checks in parallel
and persists the results on the client doc (`is_valid`, `efos`, `sat_status`):
1. **Fiscal validation** — attempts to stamp a test CFDI against the PAC. Detects RFC/legal_name/CP mismatches with SAT registry.
2. **EFOS check** — Art. 69-B blacklist lookup.
3. **SAT lists** — fan-out lookup across 20 Datos Abiertos lists (Art. 69 Cancelados/Firmes/No localizados/CSD sin efectos/..., Art. 69-B Definitivos/Presuntos/..., Art. 69-B Bis). Lists are refreshed weekly from `sat.gob.mx`.
**gigstack Connect:** Validate other teams' clients using the `team` parameter.
# Activate Descarga Masiva SAT
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/activate-descarga-masiva-sat
/openapi.json post /invoices/download/activate
Activate Descarga Masiva SAT for your team. Billing is adjusted automatically based on your plan:
- **Plan includes feature** (`needs_activation` status): Activation is free — you only pay $0.20 MXN per XML downloaded.
- **Plan without feature** (`needs_addon` status): Activation adds only the $0.20 MXN per XML download meter to your subscription. There is no monthly base fee.
Once activated, upload your FIEL via `POST /invoices/download/fiel` to complete setup.
# Check activation status
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/check-activation-status
/openapi.json get /invoices/download/activate/status
Check whether Descarga Masiva SAT is activated for your team and what action (if any) is required.
**Possible statuses:**
- `active` — Already activated, FIEL setup and scheduling are available
- `needs_activation` — Your plan includes the feature; call `POST /invoices/download/activate` to turn it on (no extra charge)
- `needs_addon` — Your plan doesn't include the feature; activating adds the $0.20 MXN/XML download meter to your subscription (no monthly fee)
- `needs_upgrade` — Free plan; upgrade first at `/memberships`
# Check download request status
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/check-download-request-status
/openapi.json get /invoices/download/status/{request_id}
Check the current SAT processing status of a download request (an id from the `created` array of
`POST /invoices/download/request`).
When `status` is `completed`, `packages` lists the packages to download with
`GET /invoices/download/package/{package_id}`.
# Connect FIEL credentials from a PFX file
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/connect-fiel-credentials-from-a-pfx-file
/openapi.json post /invoices/download/pfx
Register the team's FIEL (e.firma) with the bulk-download service by uploading a
PKCS#12 / PFX bundle and its password.
> ### ⚠️ Sensitive credentials
> `pfx` and `pfx_password` are **live security credentials** — the PFX embeds the FIEL
> private key, and the password unlocks it. Together they can impersonate the taxpayer
> before the SAT.
>
> - Send them only over TLS, only to this endpoint.
> - Never log them, never put them in a URL, never commit them, never paste them into a
> shared document or ticket.
> - The example values below are **placeholders**, not usable credentials. Do not treat
> any example in this document as a real secret to copy.
>
> The server encrypts both values at rest and never returns them in any response.
The certificate is validated before anything is stored: it must parse with the supplied
password, must not be expired, and its RFC must match the team's configured RFC. Each
RFC needs its own team.
# Create bulk download request
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/create-bulk-download-request
/openapi.json post /invoices/download/request
Submit a bulk download request to the SAT. The SAT processes these asynchronously — use `GET /invoices/download/schedule/history` to poll for status updates.
**Prerequisites:** FIEL uploaded (`fiel_uploaded: true`) + business registered (`registered: true`) + Descarga Masiva activated.
**Date range:** SAT limits each request to a maximum of 1 month. For longer periods, submit one request per month.
Once the request reaches `completed` status, the invoice metadata is available in the history response (`invoiceCount`, `processedCount`, `latestIssueDate`, `earliestIssueDate`).
# Deactivate Descarga Masiva SAT
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/deactivate-descarga-masiva-sat
/openapi.json post /invoices/download/deactivate
Deactivate Descarga Masiva SAT for your team. Scheduled downloads stop immediately. If the feature was billed as an add-on, the charge is removed from your subscription (prorated). Any remaining XML downloads in the current period are still billed at period end.
# Debug registration status
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/debug-registration-status
/openapi.json get /invoices/download/debug
Returns detailed debug information about your team's Descarga Masiva setup: FIEL status, registration status, schedule config, and SAT connectivity. Intended for troubleshooting.
# Download XML package
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/download-xml-package
/openapi.json get /invoices/download/package/{package_id}
Download a package of XML files for a completed download request. Package ids come from `packages[].id`
in `GET /invoices/download/status/{request_id}`.
The ZIP is returned **base64-encoded inside JSON** (`data.content`), not as a binary response. Packages
expire after a period set by the SAT — download them promptly.
# Download XMLs for chosen CFDIs
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/download-xmls-for-chosen-cfdis
/openapi.json post /invoices/download/import
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
**This is the billed call.** Each XML costs $0.20 MXN, charged once per CFDI.
Takes UUIDs that are currently in the `metadata` stage — typically ones you found via a preview and `GET /invoices/sat?sync_state=metadata` — and queues their XML download.
**Cost confirmation.** The server always recomputes the cost; `confirm_cost_mxn` is only ever checked against it, never trusted. Send it and a mismatch returns **409** rather than charging a different amount than you were shown. Omit it and the call is allowed only up to 100 invoices; past that confirmation is required, so a large import cannot happen by accident.
Anything not importable is reported in `skipped` with a reason rather than failing the call: `not_found`, `wrong_team`, `already_imported`, `already_queued`, `is_nomina` (nómina XMLs carry employee PII and are never downloadable), `not_importable`.
# Enable SAT sync
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/enable-sat-sync
/openapi.json post /invoices/download/enable-sync
Enables automatic SAT synchronization for your team. This is a lower-level toggle — in most flows the schedule configuration (`PUT /invoices/download/schedule`) is the right endpoint to use.
# Generate PDF for a received SAT invoice
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/generate-pdf-for-a-received-sat-invoice
/openapi.json post /invoices/sat/{uuid}/pdf
Generates a PDF from the stored XML of a received SAT invoice.
The result is cached — subsequent calls return the cached PDF instantly.
Requires the XML to have been downloaded first (`hasXml: true`).
# Get a SAT invoice
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/get-a-sat-invoice
/openapi.json get /invoices/sat/{uuid}
Returns a single invoice downloaded from SAT by its UUID.
# Get download request history
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/get-download-request-history
/openapi.json get /invoices/download/schedule/history
Returns the last 20 download requests for your team, ordered by most recent first.
**Live status check:** Any pending requests (`accepted`, `processing`, `pending`) are checked against the SAT in real time before the response is returned, so you always get up-to-date statuses in a single call.
**Statuses:**
| Status | Meaning |
|--------|---------|
| `pending` | Request queued, being sent to SAT |
| `accepted` | SAT accepted the request, processing started |
| `processing` | SAT is generating the package |
| `completed` | Done — invoice metadata is available in the response |
| `failed` | SAT rejected the request (see `statusMessage`) |
| `expired` | Package expired before it was downloaded |
# Get one job with per-window detail
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/get-one-job-with-per-window-detail
/openapi.json get /invoices/download/jobs/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Same shape as the list, plus the individual month windows and their state. Useful for showing which part of a long history pull is still outstanding.
# Get SAT history sync progress
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/get-sat-history-sync-progress
/openapi.json get /invoices/download/progress
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Reports how far the SAT has handed over your invoice history.
The SAT does not deliver a history all at once. It is backfilled forward from your sync start date in month-sized windows, and until that reaches the present, a query for a recent date range succeeds and returns nothing — identical to genuinely having no invoices. This endpoint is how you tell those apart.
**Fields:**
| Field | Meaning |
|-------|---------|
| `percent` | How much of the requested history has arrived |
| `covered_through` | Last date the backfill has reached |
| `months_remaining` | Roughly how much history is still pending |
| `current` | History is close enough to the present to be usable |
| `stalled` | Backfill has not advanced in over two days |
| `enabled` | Sync is active. When `false` it will not advance on its own |
| `eta_at` | Projected completion, epoch ms. Omitted while `stalled`, since a stopped sync has no meaningful estimate |
Values are refreshed periodically in the background. Pass `refresh=true` to recompute against the provider, which takes a few seconds.
# Get schedule configuration
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/get-schedule-configuration
/openapi.json get /invoices/download/schedule
Returns the current scheduled download configuration plus FIEL and registration status. Use this to check setup progress before configuring a schedule or submitting download requests.
- `fiel_uploaded: true` — FIEL credentials are stored
- `registered: true` — Business is registered with the SAT, downloads are enabled
- `schedule` — Current schedule config, or `null` if not configured yet
- `fiel` — FIEL certificate metadata (RFC, expiry, serial number)
# Get single invoice from SAT
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/get-single-invoice-from-sat
/openapi.json get /invoices/download/invoice/{uuid}
Fetch a single invoice's XML from the SAT by UUID. Useful for retrieving a specific CFDI without submitting a full bulk download request.
# Import CFDI XMLs the team already holds
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/import-cfdi-xmls-the-team-already-holds
/openapi.json post /invoices/import
Imports up to 50 stamped CFDI XMLs and files each one by the team's RFC:
- **Team is the issuer** → stored as an invoice (`GET /invoices/{id}`), with its XML.
- **Team is the receiver** → stored with the SAT received invoices (`GET /invoices/sat`), already imported, so Descarga Masiva will not download or bill it again.
- **Neither** → rejected; nothing is written.
Nothing is billed. A UUID the team already holds is reported as `already_exists`; one held by another account as `conflict`. Received nómina XMLs are rejected because they contain employee personal data. Status is assumed `Vigente`: an XML cannot show a later cancellation.
Send each file as `xml` (the XML text) or `content` (base64).
# List recent preview and import jobs
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/list-recent-preview-and-import-jobs
/openapi.json get /invoices/download/jobs
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
The last 20 jobs for your team, newest first, each with its progress and cost estimate.
# List SAT invoices
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/list-sat-invoices
/openapi.json get /invoices/sat
Returns invoices downloaded from SAT via Descarga Masiva. Supports filtering by direction,
status, invoice type, RFC, and date range. Uses cursor-based pagination.
Requires Descarga Masiva to be activated and at least one download request to have completed.
# Preview a range of SAT history before paying for it
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/preview-a-range-of-sat-history-before-paying-for-it
/openapi.json post /invoices/download/preview
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Queues a metadata pull for a date range and reports how many CFDIs exist and what downloading them would cost.
Reading header data from the SAT costs nothing; only fetching an XML is billed. This endpoint uses that gap: it stores what it finds as browsable rows in the `metadata` stage, which you can list with `GET /invoices/sat?sync_state=metadata` and then selectively import.
Answers **202**, not 200. One month is roughly a twelve second round trip to the SAT and a full history is dozens of them, well past any HTTP timeout. Poll `GET /invoices/download/jobs/{id}` for progress.
Only one job may run per team at a time.
# Register business with SAT (manual)
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/register-business-with-sat-manual
/openapi.json post /invoices/download/register
Manually register your business with the SAT. Only needed if the automatic registration during `POST /invoices/download/fiel` failed.
**In most cases you don't need to call this directly** — `POST /invoices/download/fiel` handles registration automatically when `sync_start_date` and `phone` are provided.
Requires that FIEL credentials were already uploaded via `POST /invoices/download/fiel`.
# Retry XML download
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/retry-xml-download
/openapi.json post /invoices/sat/{uuid}/retry-xml
Manually retry the XML download for a received SAT invoice stuck in processing or error state.
# Save schedule configuration
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/save-schedule-configuration
/openapi.json put /invoices/download/schedule
Create or update the daily scheduled download. The schedule runs once per day at the specified time (America/Mexico_City timezone) and downloads invoices from the last `days_back` days.
**Prerequisites:** FIEL must be uploaded and business must be registered. Returns `400` otherwise.
**Warning:** If you include `issued` in `download_types` and your team already has SAT invoicing (CSD) configured, the response includes a `warning` field noting that issued invoices already exist in the system.
# Stop a running job
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/stop-a-running-job
/openapi.json post /invoices/download/jobs/{id}/cancel
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Marks the job cancelled. The worker checks between windows, so it stops after finishing the one in flight rather than immediately. Already-completed jobs are returned unchanged.
# Update bulk-download sync period
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/update-bulk-download-sync-period
/openapi.json put /invoices/download/sync-period
Re-register the team with the bulk-download provider so it syncs invoices from the
earliest date the service allows.
**The request body is ignored.** `sync_start_date` is always computed server-side and is
never accepted as user input — you cannot ask for an arbitrary start date.
Requires a team RFC, a prior FIEL registration, stored FIEL credentials, and a phone
number on the stored FIEL record.
# Upload FIEL credentials
Source: https://docs.gigstack.io/api-reference/descarga-masiva-sat/upload-fiel-credentials
/openapi.json post /invoices/download/fiel
Upload your FIEL (Firma Electrónica Avanzada) credentials to enable SAT bulk downloads.
**This is the main setup endpoint.** It accepts your `.cer` and `.key` files, validates them, and — if `sync_start_date` and `phone` are provided — automatically registers your business with the SAT in the same request. No separate `/register` call needed.
**What it does:**
1. Validates the certificate format, extracts your RFC, and checks it matches your gigstack team RFC
2. Verifies the certificate is not expired
3. Securely encrypts and stores your credentials
4. If `sync_start_date` + `phone` are provided → registers your business with the SAT immediately and enables sync (`registered: true` in the response)
**Request format:** `multipart/form-data`
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cert` | file | Yes | `.cer` file (DER-encoded certificate from SAT) |
| `key` | file | Yes | `.key` file (DER-encoded encrypted private key from SAT) |
| `password` | string | Yes | Password for the `.key` file |
| `sync_start_date` | string | Recommended | Start date for SAT sync (`YYYY-MM-DD`, up to 71 months back) |
| `phone` | string | Recommended | Contact phone in international format (e.g. `+5215512345678`) |
> **Note:** The FIEL is different from the CSD (Certificado de Sello Digital). The CSD is used to stamp CFDI invoices. The FIEL is used to authenticate with the SAT for bulk downloads.
# Analyze document with AI
Source: https://docs.gigstack.io/api-reference/documents/analyze-document-with-ai
/openapi.json post /documents/{id}/analyze
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Run AI extraction over the document and store the result on it under `ai_extraction`.
Only PDFs and PNG/JPEG/WEBP images can be analyzed. For PDFs the text layer is
extracted first — a scanned PDF with no selectable text is rejected with `400`.
The request body is ignored; the prompt is derived from the document's `documentType`.
# Create document
Source: https://docs.gigstack.io/api-reference/documents/create-document
/openapi.json post /documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Register a document that has already been uploaded to storage. This endpoint records
metadata — it does not accept the file itself; upload first and pass `fileUrl` and
`storagePath`.
`complianceStatus` is always set server-side to `pending_review` on create and cannot
be supplied here; change it later with `PATCH /v2/documents/{id}`.
Unknown top-level keys are rejected (`400 validation_failed`).
# Delete document
Source: https://docs.gigstack.io/api-reference/documents/delete-document
/openapi.json delete /documents/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
**Soft delete.** The document is flagged `deleted` (with `deletedAt`/`deletedBy`) rather
than removed, and its id is pulled from the `satDocuments` array of every entity it was
linked to. It stops appearing in list and get responses.
# Get document
Source: https://docs.gigstack.io/api-reference/documents/get-document
/openapi.json get /documents/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve one document. Documents belonging to another team, and soft-deleted
documents, are reported as `404` rather than `403`.
# Link document to an entity
Source: https://docs.gigstack.io/api-reference/documents/link-document-to-an-entity
/openapi.json post /documents/{id}/link
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Attach the document to an invoice, payment, receipt or client. The link is appended to
the document's `linkedEntities` and the document id is added to the entity's
`satDocuments` array.
Linking the same document to the same entity twice returns `400`.
# List documents
Source: https://docs.gigstack.io/api-reference/documents/list-documents
/openapi.json get /documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
List the team's documents, newest first. Soft-deleted documents are excluded.
`entity_type` and `entity_id` filter on the document's links. `entity_id` is applied
client-side after the query, so it only narrows the page that was already fetched —
combine it with a larger `limit` if you expect sparse matches.
# Unlink document from an entity
Source: https://docs.gigstack.io/api-reference/documents/unlink-document-from-an-entity
/openapi.json delete /documents/{id}/link
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Remove the link between the document and an entity. The document itself is not deleted.
This `DELETE` takes a **request body** identifying the entity — the same shape as the
link call.
If the entity no longer exists, the link is still removed from the document and the call
succeeds.
# Update document
Source: https://docs.gigstack.io/api-reference/documents/update-document
/openapi.json patch /documents/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Update a document's metadata and compliance review state. Only fields explicitly
present in the body are written; omitted fields are left untouched.
The file itself (`fileUrl`, `storagePath`, `fileName`, `documentType`) is immutable —
those keys are not part of the update schema and are rejected as unknown.
# Cancel invoice
Source: https://docs.gigstack.io/api-reference/invoices/cancel-invoice
/openapi.json delete /invoices/{id}
Cancel a specific invoice with SAT.
**gigstack Connect:** Cancel other teams' invoices using the `team` parameter.
# Create a batch of income invoices
Source: https://docs.gigstack.io/api-reference/invoices/create-a-batch-of-income-invoices
/openapi.json post /invoices/income/batch
Accepts up to **1,000** income invoices in one request and stamps them in the background. Each item is
exactly the body of `POST /invoices/income`, and must carry its own `idempotency_key`, unique within the
batch. To send more than 1,000 invoices, send more batches, each with its own `Idempotency-Key`.
**What happens in the request.** Every item is validated against the `POST /invoices/income` body schema,
with no I/O. An invalid item is listed in `rejected` with its reason and the rest go ahead; only an empty
or missing `invoices` array, or more than 1,000 items, refuses the whole request (`400`). The answer is
`202` with the batch in `processing`; the first items may already have started.
**What happens after.** Each accepted item is stamped by the same code as `POST /invoices/income`, with
the credential that created the batch, so it fails for the same reasons (a client that doesn't exist, a
SAT rejection, the credit limit) and consumes one credit when stamped. A temporary failure (PAC
unavailable, its answer lost) is retried automatically, up to 6 attempts per item. Items run about 10 at
a time per team. Follow the batch with `GET /invoices/income/batch/{id}`, or subscribe a webhook to
`invoice_batch.completed` (body `InvoiceBatchCompletedWebhookEvent`, signed, sent once and never
retried; see the `webhookEvent` callback of `POST /webhooks`), then read the per-item results with
`GET /invoices/income/batch/{id}/items`.
**Two levels of idempotency.**
- The `Idempotency-Key` header names the **batch**. The batch id is derived from your team, the
credential's mode and the header, so the same key with the same body returns the same batch (`200`),
and nothing is created again. The same key with a different body is `409` `idempotency_key_reused`.
Bodies are compared as sent, including key order, so resend the exact same JSON.
- Each item's `idempotency_key` names the **invoice**. It is the same key `POST /invoices/income` uses:
an invoice already issued under it, by an earlier batch or a single call, is not issued again, and the
item ends `duplicate`. So a new batch that repeats items of a previous one is safe.
**gigstack Connect:** create the batch for a connected team with the `team` parameter, and read it with the
same `team`.
**Mode.** `livemode` comes only from the credential: a test key creates a test batch.
Not supported here: file uploads (CSV/XLSX), egress invoices and payment complements. If most of your
sales are to the general public, the monthly global invoice (*factura global*) that gigstack already
produces may be all you need.
# Create draft invoice (pre-factura)
Source: https://docs.gigstack.io/api-reference/invoices/create-draft-invoice-pre-factura
/openapi.json post /invoices/draft
Create a new draft invoice (pre-factura) that can be edited and stamped later.
Only the `invoice_type` field is required. All other fields are optional, allowing you to build the invoice incrementally:
1. **Create** a draft with minimal data (`invoice_type`)
2. **Update** the draft as data becomes available (client, items, payment details)
3. **Preview** the draft to generate a PDF with "Sin Validez Fiscal" watermark
4. **Stamp** the draft when ready to finalize it into a valid CFDI
**gigstack Connect:** Create drafts for other teams using the `team` parameter.
# Create egress invoice
Source: https://docs.gigstack.io/api-reference/invoices/create-egress-invoice
/openapi.json post /invoices/egress
Create a new egress invoice (expense/credit note) with CFDI 4.0 compliance.
**gigstack Connect:** Create invoices for other teams using the `team` parameter.
# Create income invoice
Source: https://docs.gigstack.io/api-reference/invoices/create-income-invoice
/openapi.json post /invoices/income
Create a new income invoice with CFDI 4.0 compliance.
**Safe retries with `idempotency_key`.** Send your own identifier for the invoice (for example your
order id) in `idempotency_key`. gigstack claims the key before charging a credit or stamping, so
repeating the request cannot issue a second CFDI or charge twice:
- The invoice already exists: `400` with `message.duplicate: true` and `message.uuid`.
- Another request with the key is still being processed: `409` `idempotency_in_progress`. Retry later.
- The PAC's answer was lost: `503` `PAC_OUTCOME_UNKNOWN` with `retryable: true`. Retry with the same
key: the same XML and folio are sent again, so the PAC stamps it once or returns the stamp it already made.
- The PAC can't confirm an earlier attempt: `409` `STAMP_NEEDS_REVIEW`. Don't retry; contact support.
- The SAT or PAC rejected the data: `400`, and the key is free again for a corrected request.
Without an `idempotency_key` none of this applies, and after a `503` `PAC_OUTCOME_UNKNOWN`
(`retryable: false`) you can't tell whether the invoice exists: look it up before sending it again.
To issue many invoices at once, use `POST /invoices/income/batch`.
**gigstack Connect:** Create invoices for other teams using the `team` parameter.
# Create payment complement (complemento de pago)
Source: https://docs.gigstack.io/api-reference/invoices/create-payment-complement-complemento-de-pago
/openapi.json post /invoices/payment
Stamp a CFDI type "P" payment complement (complemento de pago, Pagos 2.0) that
registers one or more payments against PPD (Pago en Parcialidades o Diferido) invoices.
Each entry in `complements[].data` is a payment. Each payment links one or more PPD
invoices through `related_documents`, supplying the amount paid, the installment number
and the previous balance so the SAT can compute the remaining balance.
**Fixed by the SAT** and therefore not required in the body: the comprobante currency
(`XXX`), the receptor `UsoCFDI` (`CP01`), and the line concept. The per-payment currency
lives in each payment's `currency` field. The series defaults to the team's payments
series (`invoice_serie_payments`).
**gigstack Connect:** Create for other teams using the `team` parameter.
# Create transfer invoice (Carta Porte)
Source: https://docs.gigstack.io/api-reference/invoices/create-transfer-invoice-carta-porte
/openapi.json post /invoices/transfer
Stamp a CFDI type T (traslado) with the Carta Porte 3.1 complement, for moving goods by road
(autotransporte).
- The comprobante is fixed by the SAT: `Moneda` XXX, `Total` 0, `UsoCFDI` S01, no `MetodoPago`.
`currency`, `use`, `payment_method` and `payment_form` are ignored if sent.
- There are no `items`: the CFDI conceptos are built from `carta_porte.Mercancias.Mercancia`.
- `carta_porte` uses the SAT attribute names from CartaPorte31.xsd. Numbers may be sent as
numbers or numeric strings.
- At least one `Origen` and one `Destino`; every `Destino` needs `DistanciaRecorrida`.
`DistanciaRecorrida` is dropped from the `Origen`.
- `FiguraTransporte` is required (for `TipoFigura` 01, the operator, send `NumLicencia`).
- The series defaults to `T`.
- Only teams stamping through gigstack's own PAC (CSD uploaded) can use this endpoint.
Stamping is irreversible. Use a test API key to try it without fiscal effect.
# Delete draft invoice
Source: https://docs.gigstack.io/api-reference/invoices/delete-draft-invoice
/openapi.json delete /invoices/draft/{id}
Permanently delete a draft invoice. This also removes any generated preview files.
**Note:** Only drafts can be deleted. For stamped invoices, use the cancel endpoint instead.
**gigstack Connect:** Delete other teams' drafts using the `team` parameter.
# Generate draft preview PDF (pre-factura)
Source: https://docs.gigstack.io/api-reference/invoices/generate-draft-preview-pdf-pre-factura
/openapi.json post /invoices/draft/{id}/preview
Generate a preview PDF for a draft invoice with a **"Sin Validez Fiscal"** watermark.
This is the **pre-factura** feature: it runs the full CFDI pipeline (normalization, XML mapping, PDF generation) without actually stamping with SAT. The generated PDF is saved and can be retrieved later via `GET /invoices/draft/{id}`.
**Requirements:**
- Draft must have at least a `client` and one `item`
**What you get:**
- A PDF that looks like a real CFDI invoice
- UUID shows `PREFACTURA-0000-0000-0000-SINVALIDEZ`
- "Sin Validez Fiscal" watermark overlay
- Useful for client approval before stamping
# Get an income invoice batch
Source: https://docs.gigstack.io/api-reference/invoices/get-an-income-invoice-batch
/openapi.json get /invoices/income/batch/{id}
Returns the batch: the items rejected up front, and the progress of the accepted ones in `counts`. Poll it
until `status` is `completed`, then read `result`, not `status`, to know how it went: `completed`
(everything issued), `partially_completed` (some items have no invoice) or `failed` (none issued).
For each invoice's outcome, page through `GET /invoices/income/batch/{id}/items`.
A batch of another team, or of the other mode (a live batch read with a test key), answers `404`.
Under gigstack Connect, send the same `team` the batch was created with.
# Get draft invoice
Source: https://docs.gigstack.io/api-reference/invoices/get-draft-invoice
/openapi.json get /invoices/draft/{id}
Retrieve a specific draft invoice by ID. Includes the preview PDF (base64) if one has been generated.
**gigstack Connect:** Access other teams' drafts using the `team` parameter.
# Get egress invoice
Source: https://docs.gigstack.io/api-reference/invoices/get-egress-invoice
/openapi.json get /invoices/egress/{id}
Retrieve a specific egress invoice by ID.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# Get income invoice
Source: https://docs.gigstack.io/api-reference/invoices/get-income-invoice
/openapi.json get /invoices/income/{id}
Retrieve a specific income invoice by ID.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# Get invoice
Source: https://docs.gigstack.io/api-reference/invoices/get-invoice
/openapi.json get /invoices/{id}
Retrieve any invoice by id, regardless of its CFDI type. The type-specific routes
(`/invoices/income/{id}`, `/invoices/egress/{id}`, `/invoices/payment/{id}`) share this
handler but additionally assert the document's type and return `400` on a mismatch;
this route performs no type check.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# Get invoice files
Source: https://docs.gigstack.io/api-reference/invoices/get-invoice-files
/openapi.json get /invoices/{id}/files
Get XML and PDF files for an invoice.
**gigstack Connect:** Access other teams' invoice files using the `team` parameter.
# Get payment complement invoice
Source: https://docs.gigstack.io/api-reference/invoices/get-payment-complement-invoice
/openapi.json get /invoices/payment/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve a specific payment complement invoice by ID (CFDI type P - Complemento de Pago).
Payment complements contain details about payments made against PPD invoices, including:
- Payment amounts and dates
- Payment method and form
- Related PPD invoices
- Tax calculations on payments
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# Get transfer invoice
Source: https://docs.gigstack.io/api-reference/invoices/get-transfer-invoice
/openapi.json get /invoices/transfer/{id}
Retrieve a specific transfer invoice by ID (CFDI type T - Traslado).
# Health check
Source: https://docs.gigstack.io/api-reference/invoices/health-check
/openapi.json get /invoices/health
Liveness probe for the invoices module. Unauthenticated.
# List CFDI errors
Source: https://docs.gigstack.io/api-reference/invoices/list-cfdi-errors
/openapi.json get /invoices/errors
Retrieve a paginated and filterable list of CFDI errors from the error matrix.
Use this endpoint to search for error codes, understand error causes, and find solutions.
**Query Options:**
- Filter by exact error code using `code` parameter
- Search across all fields using `q` parameter
- Filter by error type (invoice, receiver, sender, unknown)
- Paginate results with `limit` and `page` parameters
# List draft invoices
Source: https://docs.gigstack.io/api-reference/invoices/list-draft-invoices
/openapi.json get /invoices/draft
Retrieve a paginated list of draft invoices (pre-facturas).
Drafts are incomplete invoices that have not been stamped yet. Use them to prepare invoices incrementally before finalizing.
**gigstack Connect:** Access other teams' drafts using the `team` parameter.
# List egress invoices
Source: https://docs.gigstack.io/api-reference/invoices/list-egress-invoices
/openapi.json get /invoices/egress
Retrieve a paginated list of egress invoices with powerful filtering capabilities.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
**Filtering Options:**
- Filter by creation date using comparison operators
- Filter by `status`, `series`, `folio` and `idempotency_key` (exact match only)
- Filter by metadata fields using dot or underscore notation (e.g., `metadata.order_id` or `metadata_order_id`)
# List income invoices
Source: https://docs.gigstack.io/api-reference/invoices/list-income-invoices
/openapi.json get /invoices/income
Retrieve a paginated list of income invoices with powerful filtering capabilities.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
**Filtering Options:**
- Filter by creation date using comparison operators
- Filter by `status`, `series`, `folio` and `idempotency_key` (exact match only)
- Filter by metadata fields using dot or underscore notation (e.g., `metadata.order_id` or `metadata_order_id`)
# List payment complement invoices
Source: https://docs.gigstack.io/api-reference/invoices/list-payment-complement-invoices
/openapi.json get /invoices/payment
Retrieve a paginated list of payment complement invoices (CFDI type P - Complemento de Pago).
Payment complements are used for PPD (Pago en Parcialidades o Diferido) invoices to register partial or deferred payments.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# List support documents
Source: https://docs.gigstack.io/api-reference/invoices/list-support-documents
/openapi.json get /invoices/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve all supporting documents attached to an invoice.
**gigstack Connect:** View documents for other teams' invoices using the `team` parameter.
Documents are returned sorted by creation date (newest first).
# List the items of an income invoice batch
Source: https://docs.gigstack.io/api-reference/invoices/list-the-items-of-an-income-invoice-batch
/openapi.json get /invoices/income/batch/{id}/items
One entry per **accepted** item, in request order (ascending `index`), with its status and, once issued,
its invoice. Items rejected up front are not listed; they are in the batch's `rejected`.
Cursor pagination: pass `data.next` back as `next` while `data.has_more` is `true`. The cursor is the last
`index` of the page, so pages never overlap or skip items while statuses change. Filter with `status`,
for example `status=failed` to list only what needs fixing.
# List transfer invoices
Source: https://docs.gigstack.io/api-reference/invoices/list-transfer-invoices
/openapi.json get /invoices/transfer
Retrieve a paginated list of transfer invoices (CFDI type T - Traslado with Carta Porte).
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# Resend invoice email
Source: https://docs.gigstack.io/api-reference/invoices/resend-invoice-email
/openapi.json post /invoices/{id}/send
Resend an invoice email (PDF + XML attachments) to the client and/or additional recipients.
The client's email on the invoice is always included. Extra recipients can be added via the `emails` field.
**gigstack Connect:** Send other teams' invoice emails using the `team` parameter.
# Search invoices
Source: https://docs.gigstack.io/api-reference/invoices/search-invoices
/openapi.json get /invoices/search
Full-text search across invoices using Typesense. Provides fast, typo-tolerant search capabilities.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
**Search Capabilities:**
- Search across client name, email, invoice UUID, description, and metadata
- Typo-tolerant fuzzy matching
- Paginated results
**Requirements:**
- Typesense must be configured for your team
- The `q` (or `query`) parameter is required
# Stamp draft invoice (finalize)
Source: https://docs.gigstack.io/api-reference/invoices/stamp-draft-invoice-finalize
/openapi.json post /invoices/draft/{id}/stamp
Stamp (finalize) a draft invoice into a valid CFDI with SAT.
The draft **must** have all required fields before stamping:
- `client` — a valid client with fiscal data
- `items` — at least one item
- `use` — CFDI use code (e.g., `G03` gastos en general, `S01` sin efectos fiscales)
- `payment_form` — payment form code (e.g., 03, 99)
- `payment_method` — PUE or PPD
- `currency` — currency code (e.g., MXN, USD)
After stamping:
- The draft document is deleted
- A new stamped invoice document is created with a UUID from SAT
- The response follows the same format as `POST /invoices/income`
**gigstack Connect:** Stamp other teams' drafts using the `team` parameter.
# Trigger end-of-month global invoicing
Source: https://docs.gigstack.io/api-reference/invoices/trigger-end-of-month-global-invoicing
/openapi.json post /invoices/eom/run
Manually trigger the end-of-month (EOM) global invoicing process for your team, which
groups pending receipts into global invoices.
**Two hard preconditions:**
- The API key must be **livemode**. Test keys are rejected with `403`.
- The call must happen on the **last calendar day of the month** (America/Mexico_City).
Any other day is rejected with `400`.
- The call must happen **before 23:00 America/Mexico_City**. From 23:00 on, the automatic
end-of-month run takes over and manual calls are rejected with `400`.
The request body is ignored.
The response is returned as soon as the process is *triggered* — it does not wait for
the run to finish. A validation pass runs asynchronously afterwards; failures there are
logged server-side and are not reflected in this response.
# Update draft invoice
Source: https://docs.gigstack.io/api-reference/invoices/update-draft-invoice
/openapi.json put /invoices/draft/{id}
Update an existing draft invoice with new data. Supports partial updates — only send the fields you want to change.
**gigstack Connect:** Update other teams' drafts using the `team` parameter.
# Upload support document
Source: https://docs.gigstack.io/api-reference/invoices/upload-support-document
/openapi.json post /invoices/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Upload a supporting document (contract, proof of delivery, etc.) for an invoice.
**SAT 2026 Compliance:** The Mexican tax authority (SAT) can request supporting documentation
to validate invoices. This endpoint helps maintain compliance.
**gigstack Connect:** Upload documents for other teams' invoices using the `team` parameter.
**Supported File Types:**
- PDF files (.pdf)
- Images (.png, .jpg, .jpeg, .webp)
**File Size Limit:** 10MB
# Cancel payment
Source: https://docs.gigstack.io/api-reference/payments/cancel-payment
/openapi.json delete /payments/{id}
Cancel a specific payment.
**gigstack Connect:** Cancel other teams' payments using the `team` parameter.
# Get payment
Source: https://docs.gigstack.io/api-reference/payments/get-payment
/openapi.json get /payments/{id}
Retrieve a specific payment by ID.
**gigstack Connect:** Access other teams' payments using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/api-reference/payments/health-check
/openapi.json get /payments/health
Liveness probe for the payments module. Unauthenticated.
# List payments
Source: https://docs.gigstack.io/api-reference/payments/list-payments
/openapi.json get /payments
Retrieve a paginated list of payments with powerful filtering capabilities.
**gigstack Connect:** Access other teams' payments using the `team` parameter.
**Filtering Options:**
- Filter by payment status, currency, amount
- Filter by client ID, email, tax ID (RFC), or name
- Filter by metadata fields using dot or underscore notation (e.g., `metadata.order_id` or `metadata_order_id`)
- Filter by creation date using comparison operators
# List support documents
Source: https://docs.gigstack.io/api-reference/payments/list-support-documents
/openapi.json get /payments/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve all supporting documents attached to a payment.
**gigstack Connect:** View documents for other teams' payments using the `team` parameter.
Documents are returned sorted by creation date (newest first).
# Mark payment as paid
Source: https://docs.gigstack.io/api-reference/payments/mark-payment-as-paid
/openapi.json post /payments/{id}/paid
Mark a payment as paid with the specified payment form.
**gigstack Connect:** Mark other teams' payments as paid using the `team` parameter.
## Required Information
- **payment_form** (required): SAT-compliant payment form code (`01`–`31`, `99`)
- **date** (optional): when the payment was received, in **Unix epoch milliseconds**
(13 digits). The handler passes the value straight to `Luxon.fromMillis()` and
compares it against `Luxon.now().toMillis()`; a seconds-based timestamp resolves to
1970 and is silently accepted. Defaults to now. A future date returns `400`.
- **send_email** / **ignore_emails** (optional): `ignore_emails` takes precedence —
the handler resolves `ignore_emails ?? (send_email === false)`, so `ignore_emails: true`
suppresses notifications even when `send_email: true`.
- **amount_received** (optional): cumulative amount received so far, in the payment's
currency. Omit it, or send the full payment amount, to mark the payment `succeeded`
(unchanged default behavior). Send less than the full amount to record a partial
top-up: the payment is set to `partially_paid` instead, triggering a partial payment
complement on the related PPD invoice. Requires the team's
`automatePartialPaymentComplements` default to be on and a `payment_complement`
automation already present on the payment. Sending more than the payment amount
returns `400`.
# Refund payment
Source: https://docs.gigstack.io/api-reference/payments/refund-payment
/openapi.json post /payments/{id}/refund
Refund a payment with a specified reason and amount.
**gigstack Connect:** Refund other teams' payments using the `team` parameter.
## Key Features
- Partial or full refunds supported
- Optional external processor refund handling
- Automatic refund tracking and reporting
- Supports Stripe integration for automatic processor refunds
`reason` and `amount` are both required. `amount` must be at least **0.01** — the
validator rejects anything below it.
# Register payment
Source: https://docs.gigstack.io/api-reference/payments/register-payment
/openapi.json post /payments/register
Register a payment with optional automation for invoice creation.
**gigstack Connect:** Register payments for other teams using the `team` parameter.
## Automation Types
Control what happens automatically when registering a payment:
- **`pue_invoice`**: Creates a PUE (Pago en Una sola Exhibición) invoice immediately
- **`none`**: No automation, registers payment only
## PPD Invoice Linking
You can link a payment to an existing PPD (Pago en Parcialidades o Diferido) invoice by providing the `ppd_invoice_id` field.
When set, a payment complement (complemento de pago) CFDI will be automatically generated and linked to the PPD invoice.
The referenced invoice must have `payment_method='PPD'` and `status='valid'`.
## Payment Form
The `payment_form` field specifies the Mexican SAT payment form code:
Common codes include: `01` (cash), `02` (check), `03` (electronic transfer), `04` (credit card), etc.
The payment will be marked as 'succeeded' immediately upon registration.
## Required fields
`client`, `currency`, `items` (at least one), `payment_form` and **`automation_type`**
are required. `automation_type` has no default — omitting it fails validation.
Allowed values: `pue_invoice`, `ppd_invoice_and_complement`, `none`.
## `date`
Optional, in **Unix epoch milliseconds** (13 digits). Compared against
`Luxon.now().toMillis()`; a future value returns `400`.
## `transfer_data` — all-or-nothing
`transfer_data` is optional, but **when it is present all four of `master`, `connect`,
`master_to` and `connect_to` are required**; omitting any one fails validation.
| field | type | constraint |
|---|---|---|
| `master` | number | required, `0 ≤ master ≤ 100` (percentage retained by the master team) |
| `connect` | string | required, non-empty — RFC of the connected team |
| `master_to` | enum | required — `client` or `connect` |
| `connect_to` | enum | required — `client` or `master` |
| `connect_custom_config` | object | optional; every field inside it is optional except `type`, `rate` and `withholding` on each `taxes[]` entry |
## `invoice_config`
All nested fields are optional:
| field | type | meaning |
|---|---|---|
| `serie` | string | invoice series |
| `folio` | number | invoice folio number |
| `date` | number | invoice issue date, Unix epoch **milliseconds** |
| `global.year` | number | fiscal year of the global (EOM) invoice, e.g. `2026` |
| `global.months` | string | SAT `c_Meses` code, e.g. `01` for January or `13` for Jan–Feb |
| `global.periodicity` | string | SAT `c_Periodicidad` code — `01` daily, `02` weekly, `03` fortnightly, `04` monthly, `05` bimonthly |
| `validUntil` | number | expiry of the self-invoicing window, Unix epoch **milliseconds** |
## Email suppression
`ignore_emails: true` suppresses notification emails. On this endpoint `send_email` is
accepted but has no effect — only `ignore_emails` is persisted onto the payment.
## Unknown fields
Body validation runs in strict allowlist mode — any undeclared key is rejected with
`400 validation_failed` / `unexpected_key`.
# Request payment
Source: https://docs.gigstack.io/api-reference/payments/request-payment
/openapi.json post /payments/request
Create a payment request that creates a payment in 'requires_payment_method' status.
**gigstack Connect:** Create payment requests for other teams using the `team` parameter.
## Payment Request Flow
This endpoint creates a payment request that customers can complete using various payment methods.
The payment will be created with status 'requires_payment_method'.
## Allowed Payment Methods
`allowed_payment_methods` is required. The validator accepts exactly these five values:
- **`card`** — credit/debit card
- **`bank`** — Mexican bank transfer (SPEI)
- **`oxxo`** — OXXO convenience store
- **`stripe-spei`** — Stripe customer balance
- **`mercadopago-wallet`** — Mercado Pago wallet
The handler then narrows the list per processor and rejects anything outside the
processor's own set:
| `payment_processor` | accepted methods | currency |
|---|---|---|
| `stripe` (default) | `card`, `oxxo`, `bank`, `stripe-spei` | any |
| `mercadopago` | `card`, `oxxo`, `mercadopago-wallet` | `MXN` only |
| `openpay` | `card`, `bank_account`, `store` | `MXN` only |
| `pagoralia` | `hosted`, `card`, `oxxo` | `MXN` only |
| `conekta` | `hosted`, `card`, `oxxo`, `spei` | `MXN` only |
> The processor-specific names in the right-hand column (`bank_account`, `store`,
> `hosted`, `spei`) are **not** accepted by body validation — only the five enum values
> above pass, so those processors are effectively limited to their overlap with the enum.
## Required fields
`client`, `currency`, `allowed_payment_methods`, `items` and **`automation_type`** are
all required. `automation_type` has no default: omitting it fails validation with
`missing_required`. Allowed values: `pue_invoice`, `ppd_invoice_and_complement`, `none`.
## Email suppression
`ignore_emails` takes precedence over `send_email`: the handler stores
`ignore_emails ?? (send_email === false)`, so `ignore_emails: true` suppresses
notifications regardless of `send_email`.
## Unknown fields
Body validation runs in strict allowlist mode — any key not declared in the schema is
rejected with `400 validation_failed` / `unexpected_key`.
# Search payments
Source: https://docs.gigstack.io/api-reference/payments/search-payments
/openapi.json get /payments/search
Full-text search across payments using Typesense. Provides fast, typo-tolerant search capabilities.
**gigstack Connect:** Access other teams' payments using the `team` parameter.
**Search Capabilities:**
- Search across client name, email, payment ID, description, and metadata
- Typo-tolerant fuzzy matching
- Filter search results by status, currency, or client ID
- Paginated results
**Requirements:**
- Typesense must be configured for your team
- The `q` (or `query`) parameter is required
# Update payment
Source: https://docs.gigstack.io/api-reference/payments/update-payment
/openapi.json put /payments/{id}
Update a payment. The endpoint supports modifying the `description` of items, attaching `automation_type` to a payment that has no automations, and patching the embedded `client` (with the change propagated to the `/clients/{id}` master record). The body must include at least one of `items`, `automation_type` or `client`.
**gigstack Connect:** Update other teams' payments using the `team` parameter.
## What can be updated
- **Item description**: For each entry in `items`, the item is matched by `id` inside the payment and its `description` is replaced. Any other field on the item (taxes, discounts, quantity, unit_price, etc.) is rejected by validation. Item totals, taxes and `itemsAmounts` are not recalculated.
- **Automations**: `automation_type` is only accepted when the payment has no existing automations. If the payment already has automations, the request returns 400. The same enum values used by `POST /payments/register` apply (`pue_invoice`, `ppd_invoice_and_complement`, `none`).
- **Client**: The `client` object accepts a partial patch (`name`, `company`, `phone`, `email`, `bcc`, `metadata`, `legal_name`, `tax_id`, `use`, `tax_system`, `address`). The client `id` cannot be modified. Each provided field is written both to the `client` embedded in the payment and to the `/clients/{id}` master document via a partial merge. Fiscal/SAT validation is not re-run from this endpoint — call `PUT /clients/{id}` if full re-validation is needed.
## Trigger re-fire for already succeeded payments
When non-empty automations are added (i.e. `automation_type` is `pue_invoice` or `ppd_invoice_and_complement`) and the payment is already `succeeded`, the endpoint performs a second write that sets `status` to `succeeded_` so that the downstream automation trigger (which fires on transitions into `succeeded`) can re-fire on a subsequent flip back to `succeeded`.
## Allowed payment statuses
The endpoint accepts updates regardless of payment status (including `succeeded` and `cancelled`) so descriptions can be corrected after the fact. Note that this endpoint does not re-issue or modify any CFDI already linked to the payment.
# Upload support document
Source: https://docs.gigstack.io/api-reference/payments/upload-support-document
/openapi.json post /payments/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Upload a supporting document (contract, proof of delivery, etc.) for a payment.
**SAT 2026 Compliance:** The Mexican tax authority (SAT) can request supporting documentation
to validate invoices and payments. This endpoint helps maintain compliance.
**gigstack Connect:** Upload documents for other teams' payments using the `team` parameter.
**Supported File Types:**
- PDF files (.pdf)
- Images (.png, .jpg, .jpeg, .webp)
**File Size Limit:** 10MB
**Document Types:**
- `contract`: Service or product contracts
- `delivery_proof`: Proof of delivery or service completion
- `payment_proof`: Payment receipts or confirmations
- `communication`: Emails, messages, or agreements
- `payment_confirmation`: Payment processor confirmations
- `subscription_info`: Subscription or recurring service details
# Confirm a platform payouts run for stamping
Source: https://docs.gigstack.io/api-reference/platform-payouts/confirm-a-platform-payouts-run-for-stamping
/openapi.json post /platform-payouts/{id}/confirm
**Irreversible.** Hands a `plan_ready` run to the background worker, which stamps its CFDIs (it runs
every minute). A stamped CFDI can only be cancelled, not undone. The response only acknowledges the
hand-off; follow progress with `GET /platform-payouts/{id}` until `status` is `completed` or
`failed`, then read its `result`.
**Safe to retry.** Confirming a run that is already `stamping` or `completed` returns its current
status with `200` and does nothing else.
**Provider-months already certified.** Before the run flips to `stamping`, each provider-month it
certifies is reserved. If an earlier run of your team (same mode) already reserved a provider-month,
this run's retention certificates for it are skipped (their `reason` names the earlier run,
`reason_code` is `certificate_month_reserved`) and the counts in `included_count`,
`excluded_count`, `exclusion_summary`, `exclusion_code_summary` and
`planned_documents.certificate` are recomputed. Income and commission invoices are not affected.
No request body. Requires `editor` permission on invoices for user-scoped tokens.
# Get a platform payouts run
Source: https://docs.gigstack.io/api-reference/platform-payouts/get-a-platform-payouts-run
/openapi.json get /platform-payouts/{id}
Returns the run: its status, plan totals and, once confirmed, the worker's progress. Poll it after
confirming until `status` is `completed` or `failed`; the worker runs every minute. Then read
`result`, not `status`, to know how the run went: `status: completed` only means the worker has
nothing left to try, while `result` tells `completed` (everything issued) from
`partially_completed` (some documents failed) and `failed` (nothing issued).
A run of another team, or of the other mode (a live run with a test key), answers `404`
`run_not_found`, never `403`.
# Health check
Source: https://docs.gigstack.io/api-reference/platform-payouts/health-check
/openapi.json get /platform-payouts/health
Liveness probe for the platform payouts module. Unauthenticated.
# List a platform payouts run's movements
Source: https://docs.gigstack.io/api-reference/platform-payouts/list-a-platform-payouts-runs-movements
/openapi.json get /platform-payouts/{id}/movements
One entry per row of the movements file, in file order, with the state of its income invoice and
retention certificate. Use it to review exclusions before confirming, and to find failures after.
Cursor pagination: pass `data.next` back as `next` while `data.has_more` is `true`. The cursor is
stable while the worker updates statuses, so pages never overlap or skip rows.
Commission invoices (one per provider-month) are not listed here; the run only reports their counts.
# Upload files and plan a platform payouts run
Source: https://docs.gigstack.io/api-reference/platform-payouts/upload-files-and-plan-a-platform-payouts-run
/openapi.json post /platform-payouts
Uploads the **movements** and **commissions** files and computes the plan: which CFDIs will be
issued for each provider, and why any row is excluded. Nothing is stamped here; review the plan with
`GET /platform-payouts/{id}` and `GET /platform-payouts/{id}/movements`, then call
`POST /platform-payouts/{id}/confirm`.
**Who can call it.** The credential's team must be a marketplace **master team** whose billing
account has platform payouts enabled. API keys and OAuth tokens act as the team; user-scoped
tokens (MCP, dashboard) must belong to the team and hold `editor` permission on invoices.
Sending gigstack Connect's `team` parameter moves the request to a connected team, which is not a
master team, so it is refused with `not_master_team`. These checks (`403` `not_master_team`,
`no_billing_account`, `feature_disabled`, `team_not_found`, `not_a_member`) run right after the
`Idempotency-Key` check and **before the upload is read**: a refused request's files are never
processed or stored.
**Planning is synchronous.** The response carries the run with status `plan_ready`, or
`plan_failed` with a Spanish `error` (unreadable file, missing columns, empty file, too many rows).
Row-level problems do not fail the plan: the row becomes an excluded movement with a reason.
**Idempotent on `Idempotency-Key` (required).** The run id is derived from your team, the key's
mode and the header value, and the run records a fingerprint of the two files' **contents** (their
bytes; file names don't count). The first request creates the run (`201`). A later request with the
same key and the same files returns that same run (`200`), whatever its status, without planning
again. The same key with **different** files is refused with `409 idempotency_key_reused`. Runs
created before the fingerprint existed have none and are returned as before (`200`) whatever files
you send. A retry that arrives while the first request is still planning gets the run in
`planning`: poll `GET /platform-payouts/{id}`. A failed plan stays failed under its key: fix the
file and send a **new** key.
**Tax policy.** Which documents each provider gets, and their SAT keys and withholding rates,
come from your master account's policy, set at onboarding (defaults: ground passenger transport,
régimen `625`, CSD required, service type `01`, 2.1% ISR). Contact support to configure it.
**Mode.** `livemode` comes only from the credential: a test key creates a test run.
# Cancel receipt
Source: https://docs.gigstack.io/api-reference/receipts/cancel-receipt
/openapi.json delete /receipts/{id}
Cancel a receipt. This action cannot be undone.
**gigstack Connect:** Cancel other teams' receipts using the `team` parameter.
# Create receipt
Source: https://docs.gigstack.io/api-reference/receipts/create-receipt
/openapi.json post /receipts
Create a new receipt with items and client information. Receipts are pre-invoice documents
that can be later stamped as CFDI invoices.
**Features:**
- Automatic amount calculations with taxes
- Flexible validity periods
- Client auto-creation support
- Metadata support for tracking
- Idempotency support to prevent duplicate receipts
**gigstack Connect:** Create receipts for other teams using the `team` parameter.
# Get receipt
Source: https://docs.gigstack.io/api-reference/receipts/get-receipt
/openapi.json get /receipts/{id}
Retrieve a specific receipt by ID.
**gigstack Connect:** View other teams' receipts using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/api-reference/receipts/health-check
/openapi.json get /receipts/health
Liveness probe for the receipts module. Unauthenticated.
# List receipts
Source: https://docs.gigstack.io/api-reference/receipts/list-receipts
/openapi.json get /receipts
Retrieve a paginated list of receipts.
**gigstack Connect:** View other teams' receipts using the `team` parameter.
**Filters:** only `client_id`, `tax_id` and the `created[...]` range are applied. `status`, `valid_until` and
`metadata` are accepted but **ignored** — they do not narrow the results. Filter on those fields client-side.
# Reopen receipt
Source: https://docs.gigstack.io/api-reference/receipts/reopen-receipt
/openapi.json post /receipts/{id}/reopen
Return a receipt whose self-invoicing failed to `status: pending`, so the client can
try again from the self-invoicing portal.
Only a receipt that is **not** `pending` and has **no** entries in `invoices[]` can be
reopened: an already-pending receipt and one that produced a CFDI are both rejected
with `409`. The response clears `automatic_invoice_error`; who reopened it and why are
recorded on the receipt but are not part of the API response.
**gigstack Connect:** reopen other teams' receipts with the `team` parameter.
# Search receipts
Source: https://docs.gigstack.io/api-reference/receipts/search-receipts
/openapi.json get /receipts/search
Full-text search across receipts using Typesense. Provides fast, typo-tolerant search capabilities.
**gigstack Connect:** Access other teams' receipts using the `team` parameter.
**Search Capabilities:**
- Search across client name, email, receipt description, and metadata
- Typo-tolerant fuzzy matching
- Paginated results
**Requirements:**
- Typesense must be configured for your team
- The `q` (or `query`) parameter is required
# Stamp receipt
Source: https://docs.gigstack.io/api-reference/receipts/stamp-receipt
/openapi.json post /receipts/{id}/stamp
Convert a receipt into a CFDI invoice by stamping it with SAT.
Only receipts with `status: pending` can be stamped. A receipt that already has
entries in `invoices[]` is returned as-is with a `200` and no new CFDI; any other
non-pending receipt (a cancelled one is stored as `completed`) is rejected with a
`409`.
**Stamp Options:**
- `client`: Stamp to the associated client
- `general_public_national`: Stamp to Mexican general public
- `general_public_foreign`: Stamp to foreign general public
**gigstack Connect:** Stamp other teams' receipts using the `team` parameter.
# Cancel retention
Source: https://docs.gigstack.io/api-reference/retentions/cancel-retention
/openapi.json delete /retentions/{id}
Cancel a stamped tax retention document with the SAT.
**Cancellation motives** (SAT catalog):
- `01` - Comprobante emitido con errores con relación
- `02` - Comprobante emitido con errores sin relación
- `03` - No se llevó a cabo la operación
- `04` - Operación nominativa relacionada en una factura global
**gigstack Connect:** Cancel other teams' retentions using the `team` parameter.
# Create retention
Source: https://docs.gigstack.io/api-reference/retentions/create-retention
/openapi.json post /retentions
Create and stamp a new tax retention document (CFDI Retenciones 2.0).
The API accepts a **simplified format** — the backend handles:
- **Client lookup** by ID (fetches RFC, legal name, address automatically)
- **Nationality detection** from client's country
- **Tax code mapping** (`ISR` → 001, `IVA` → 002, `IEPS` → 003)
- **Payment type defaults** per tax (ISR → provisional, IVA/IEPS → definitivo)
- **Totals auto-calculation** (taxable = operation − exempt, retained = sum of taxes)
- **Folio auto-generation**
- SAT stamping, PDF and XML generation
**gigstack Connect:** Create retentions for other teams using the `team` parameter.
## Conditional requirements per `retention_key`
`retention_key` is validated as a free-form string — any SAT key is accepted — but three
keys carry extra requirements enforced before the document is stamped. A violation
returns `400` with `error.code: invalid_request_body` and the message quoted below.
| `retention_key` | additional requirement | error message on violation |
|---|---|---|
| `16` — Intereses | `interest` object is required | `interest object is required for retention key 16 (Intereses)` |
| `25` — Otro tipo de retenciones | `retention_description` is required and non-empty | `retention_description is required for retention key 25 (Otro tipo de retenciones)` |
| `26` — Plataformas Tecnológicas | `platform_services` object is required **and** `taxes` must contain at least one entry whose `tax` is not `IVA` (i.e. `ISR` or `IEPS`) | `platform_services object is required for retention key 26 (Plataformas Tecnológicas)` / `At least one non-IVA tax (ISR or IEPS) is required for Plataformas Tecnológicas` |
Every other key requires only the base fields (`retention_key`, `client`,
`period_start`, `period_end`, `period_year`, `total_operation`, `taxes`).
Within `interest`, `financial_system`, `nominal_interest` and `real_interest` are
required. Within `platform_services`, `periodicity` and `services` are required, and
each entry in `services` requires `payment_form`, `service_type`, `service_date` and
`price_without_tax`.
> Unlike the other modules, this endpoint **strips** unknown keys instead of rejecting
> them (`stripUnknown: true`), so an undeclared field is silently discarded rather than
> returning `400`.
# Get retention
Source: https://docs.gigstack.io/api-reference/retentions/get-retention
/openapi.json get /retentions/{id}
Retrieve a specific tax retention document by ID (UUID).
**gigstack Connect:** View other teams' retentions using the `team` parameter.
# Get retention files
Source: https://docs.gigstack.io/api-reference/retentions/get-retention-files
/openapi.json get /retentions/{id}/files
Retrieve PDF and/or XML files for a stamped tax retention document.
Use `file_type` query parameter to request specific file types.
**gigstack Connect:** Access other teams' retention files using the `team` parameter.
# List retentions
Source: https://docs.gigstack.io/api-reference/retentions/list-retentions
/openapi.json get /retentions
Retrieve a paginated list of tax retention documents (CFDI Retenciones 2.0).
**gigstack Connect:** View other teams' retentions using the `team` parameter.
# Check an RFC against the SAT lists
Source: https://docs.gigstack.io/api-reference/sat-lists/check-an-rfc-against-the-sat-lists
/openapi.json get /sat-lists/check/{rfc}
Checks whether an RFC appears on any of the SAT lists gigstack tracks, and returns every matching entry.
Use this before invoicing a counterparty: `is_risky` is `true` when the RFC appears on at least one list
flagged as risky (`Cancelados`, `Definitivos 69-B`, `Presuntos 69-B`, `No localizados`, `CSD sin efectos`, …),
and `risky_lists` names exactly which ones.
An RFC that appears on no list returns `200` with `found: false` — a clean RFC is not a `404`.
# Consult the SAT "Opinión del Cumplimiento" (32-D) for an RFC
Source: https://docs.gigstack.io/api-reference/sat-lists/consult-the-sat-"opinión-del-cumplimiento"-32-d-for-an-rfc
/openapi.json get /sat-lists/32d/{rfc}
Consults the SAT's public *Opinión del Cumplimiento de Obligaciones Fiscales* (Artículo 32-D) service
for an RFC and, when the SAT publishes one, returns a link to the constancia PDF.
### How to read the result — please read before building on this
The SAT's public service **only ever publishes positive opinions**, and only for taxpayers who
explicitly authorized public disclosure of their opinion. There are exactly two outcomes:
- `status: "positiva"` (`found: true`) — the SAT publishes a positive opinion for this RFC, and
`pdf_url` links to the constancia.
- `status: "no_autorizado"` (`found: false`) — the SAT publishes nothing for this RFC. **This means
the result is unknown.** Either the taxpayer never opted in to public disclosure, or no opinion is
published. It is **not** a negative opinion, it is **not** evidence of non-compliance, and it must
never be shown to a user as "opinión negativa", "incumplido", or anything equivalent. The only
correct reading is "the SAT does not publish an opinion for this RFC".
There is no third status: the SAT never exposes negative opinions through this service, so this
endpoint can never tell you that a taxpayer is non-compliant.
A failure reaching the SAT — network error, or the SAT changing its page — returns `500`. It is never
collapsed into `no_autorizado`, so `no_autorizado` always reflects a real answer from the SAT.
# List SAT lists and sync status
Source: https://docs.gigstack.io/api-reference/sat-lists/list-sat-lists-and-sync-status
/openapi.json get /sat-lists
Returns every SAT list definition tracked by gigstack, together with the metadata from its most recent sync.
gigstack mirrors the RFC lists the SAT publishes under **Artículo 69**, **Artículo 69-B** and **Artículo 69-B Bis**.
The lists are re-synced automatically every Sunday from the SAT's published CSVs.
Each list carries an `is_risky` flag. Risky lists (for example `Cancelados`, `Definitivos 69-B`, `No localizados`,
`CSD sin efectos`) are the ones that indicate a counterparty you should not invoice; the remaining lists are
informational only.
The `sync` object is `null` for a list that has never completed a sync.
# Create service
Source: https://docs.gigstack.io/api-reference/services/create-service
/openapi.json post /services
Create a new service.
**gigstack Connect:** Create services for other teams using the `team` parameter.
# Delete service
Source: https://docs.gigstack.io/api-reference/services/delete-service
/openapi.json delete /services/{id}
Delete a specific service.
**gigstack Connect:** Delete other teams' services using the `team` parameter.
# Get service
Source: https://docs.gigstack.io/api-reference/services/get-service
/openapi.json get /services/{id}
Retrieve a specific service by ID.
**gigstack Connect:** Access other teams' services using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/api-reference/services/health-check
/openapi.json get /services/health
Liveness probe for the services module. Unauthenticated.
# List services
Source: https://docs.gigstack.io/api-reference/services/list-services
/openapi.json get /services
Retrieve a paginated list of services.
**gigstack Connect:** Access other teams' services using the `team` parameter.
# Update service
Source: https://docs.gigstack.io/api-reference/services/update-service
/openapi.json put /services/{id}
Update an existing service.
**gigstack Connect:** Update other teams' services using the `team` parameter.
# Add team member
Source: https://docs.gigstack.io/api-reference/teams/add-team-member
/openapi.json post /teams/{id}/add-member
Add a member to a team.
**gigstack Connect:** Add members to other teams using the `team` parameter.
# Create a portal access token
Source: https://docs.gigstack.io/api-reference/teams/create-a-portal-access-token
/openapi.json post /teams/{id}/portal-access-token
Mint a short-lived, read-only access token for the team's public portal, and get a ready-to-share magic link.
The link opens the gigstack public portal (embeded.gigstack.pro) where the team can list and download its issued live-mode invoices, branded with your master team's logo and colors. The token cannot create, modify or cancel anything, and it only grants the scopes you request.
**Important:** Minting a token for a team other than your own is only available for gigstack Connect accounts (master teams), and the target team must belong to your billing account.
## Use Cases
- Embed an "invoices" section for your sub-teams inside your own product
- Generate on-demand links so a sub-team can review its issued invoices without a gigstack login
## Recommendations
- Generate the link at click time and redirect the user to it; do not store it or send it by email
- Use the default 1h expiry unless you have a longer-lived embedded session
# Create team
Source: https://docs.gigstack.io/api-reference/teams/create-team
/openapi.json post /teams
Create a new team.
Requires the `multipleIssuerAccounts` feature on your plan → `403 insufficient_permissions` without it.
**gigstack Connect:** Create teams using the `team` parameter.
Requires a plan with the "Proveedores" feature: otherwise the call answers `401` with
`You should subscribe to a plan that allows "Proveedores" feature.` (standardized envelope).
# Create team series
Source: https://docs.gigstack.io/api-reference/teams/create-team-series
/openapi.json post /teams/{id}/series
Create a series for a team.
**gigstack Connect:** Create series for other teams using the `team` parameter.
# Delete team
Source: https://docs.gigstack.io/api-reference/teams/delete-team
/openapi.json delete /teams/{id}
Schedule a team for deletion (soft delete).
The team's `status` is set to `pending_deletion` and a hard deletion is scheduled **30 days** from now.
During this grace period the team is retained for recovery purposes. All members are removed from the team,
and each member's `teams` array is updated accordingly. A member's `billingAccount` is cleared when no
other team of theirs shares it.
Requires the `multipleIssuerAccounts` feature on your plan → `403 insufficient_permissions` without it.
## Preconditions
The team **cannot** be deleted when any of the following are true:
- The team already has `status = pending_deletion` → `409 resource_conflict`.
- The team has existing `invoices`, `payments`, or `receipts` → `400 business_rule_violation`.
- The team has any active integration (`completed = true`) among:
`stripe`, `mercadopago`, `paypal`, `openpay`, `conekta`, `clip`, `bank`, `shopify`, `whmcs`, `hilos`
→ `400 business_rule_violation`.
**gigstack Connect:** Delete other teams using the `team` parameter.
# Get team
Source: https://docs.gigstack.io/api-reference/teams/get-team
/openapi.json get /teams/{id}
Retrieve a specific team by ID.
**gigstack Connect:** Access other teams using the `team` parameter.
# Get team integrations (not implemented)
Source: https://docs.gigstack.io/api-reference/teams/get-team-integrations-not-implemented
/openapi.json get /teams/integrations
**Not yet available.** The route is registered and reachable — it is declared before
`/teams/{id}` so it is no longer shadowed by the team-lookup route — but the handler is
still a stub that returns `501 Not Implemented` for every request. It never returns
integration data.
Do not build against this endpoint yet. This entry exists so the published surface
matches the deployed behavior.
# Get team onboarding URL
Source: https://docs.gigstack.io/api-reference/teams/get-team-onboarding-url
/openapi.json get /teams/{id}/onboarding-url
Generate a secure onboarding URL for team setup and configuration.
**Important:** This endpoint is only available for gigstack Connect accounts (master teams).
## Use Cases
- Generate onboarding links for new teams
- Allow secure team configuration setup
- Enable embedded team management flows
**gigstack Connect:** Generate onboarding URLs for other teams using the `team` parameter.
# Get team series
Source: https://docs.gigstack.io/api-reference/teams/get-team-series
/openapi.json get /teams/{id}/series
Get series for a team.
**gigstack Connect:** Get series for other teams using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/api-reference/teams/health-check
/openapi.json get /teams/health
Liveness probe for the teams module. Unauthenticated.
# List teams
Source: https://docs.gigstack.io/api-reference/teams/list-teams
/openapi.json get /teams
Retrieve a paginated list of teams.
**gigstack Connect:** Access other teams using the `team` parameter.
# Remove team member
Source: https://docs.gigstack.io/api-reference/teams/remove-team-member
/openapi.json post /teams/{id}/remove-member
Remove a member from a team.
**gigstack Connect:** Remove members from other teams using the `team` parameter.
# Sign manifest document
Source: https://docs.gigstack.io/api-reference/teams/sign-manifest-document
/openapi.json post /teams/{id}/manifest/sign
Signs a manifest document (Carta Manifiesto) using the FIEL (Firma Electrónica Avanzada) for SAT compliance.
This endpoint is used to sign the authorization manifest that authorizes the PAC (Proveedor Autorizado de Certificación)
to issue CFDI invoices on behalf of your team's RFC. The manifest must be signed to grant the PAC permission to stamp
and process invoices under your team's tax identification.
**Important Notes:**
- Your SAT configuration must be completed before signing the manifest
- The FIEL certificate must be valid and issued by SAT
- The certificate must match your team's RFC
- Once signed, the manifest is stored in your team's SAT configuration
- The manifest includes both XML and PDF files
**Supported Formats:**
1. **JSON format (application/json):**
- Send Base64 encoded certificate files
- Useful for API integrations
2. **Form Data format (multipart/form-data):**
- Upload certificate files directly
- Useful for web form submissions
**Note:** The `team` and `livemode` parameters are automatically extracted from your JWT token and applied to the request.
You do not need to include these fields in the request body.
# Update team
Source: https://docs.gigstack.io/api-reference/teams/update-team
/openapi.json put /teams/{id}
Update an existing team.
**gigstack Connect:** Update other teams using the `team` parameter.
# Update team series
Source: https://docs.gigstack.io/api-reference/teams/update-team-series
/openapi.json put /teams/{id}/series/{seriesId}
Update a team series.
**gigstack Connect:** Update series for other teams using the `team` parameter.
# Update team settings
Source: https://docs.gigstack.io/api-reference/teams/update-team-settings
/openapi.json put /teams/{id}/settings
Update team settings including defaults for invoicing, taxes, series, and email configurations.
**gigstack Connect:** Update settings for other teams using the `team` parameter.
## Team Settings Configuration
This endpoint allows you to configure various team-wide defaults and behaviors:
- **Invoice Settings:** Default descriptions, PDF notes, product keys
- **Tax Configuration:** Default taxes for MXN and USD currencies
- **Email Settings:** BCC recipients, email preferences
- **CFDI Configuration:** Default series, uses, product/unit keys
- **Automation:** Payment complement automation for PPD invoices
# Upload SAT CSD certificates
Source: https://docs.gigstack.io/api-reference/teams/upload-sat-csd-certificates
/openapi.json post /teams/{id}/sat-connection
Upload SAT CSD (Certificado de Sello Digital) certificates to establish SAT connection for CFDI invoicing.
This endpoint accepts multipart form data with the certificate files and password.
## Required Files
- **cert**: Certificate file (.cer) - The public certificate
- **key**: Private key file (.key) - The encrypted private key
- **keyPass**: Password for the private key
## First-Time Connection
When this is the first SAT connection for a team (no previous SAT setup),
the system will automatically initialize default invoice series (G, NC, P, T).
**gigstack Connect:** Upload SAT certificates for other teams using the `team` parameter.
# Create user
Source: https://docs.gigstack.io/api-reference/users/create-user
/openapi.json post /users
Create a new user.
**gigstack Connect:** Create users for other teams using the `team` parameter.
# Delete user
Source: https://docs.gigstack.io/api-reference/users/delete-user
/openapi.json delete /users/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Remove a user from your teams, and delete their account when it is yours.
The user must be a member of the team the request is authenticated for. They are removed
from every team of your billing account you administer: your own team, and (for a
master-team API key, or a dashboard admin) its sibling teams. Memberships in teams you
do not administer are left untouched.
The login (Firebase Auth) and the `users/{id}` document are deleted only when that
leaves the user in no team at all **and** the account belongs to your billing account
(created by it, or tied to no other billing account, and not the owner of one). Otherwise
the user keeps their login, and `account_deleted` is `false`. An Auth deletion failure is
logged but does not fail the request.
**You cannot delete yourself.** If the id matches the user behind the API key the call
is rejected with `400` / `business_rule_violation`.
**gigstack Connect:** Delete other teams' users using the `team` parameter.
# Generate login link
Source: https://docs.gigstack.io/api-reference/users/generate-login-link
/openapi.json post /users/login-link
Generate a login link for a user. The link contains a custom Firebase token that allows the user to authenticate directly.
**Requirements:**
- User must have been created via API (`from: 'api'`)
- User must belong to the billing account making the request
If requirements are not met, returns a 404 error.
**gigstack Connect:** Generate login links for other teams' users using the `team` parameter.
# Get user
Source: https://docs.gigstack.io/api-reference/users/get-user
/openapi.json get /users/{id}
Retrieve a specific user by ID.
**gigstack Connect:** Access other teams' users using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/api-reference/users/health-check
/openapi.json get /users/health
Liveness probe for the users module. Unauthenticated.
# List users
Source: https://docs.gigstack.io/api-reference/users/list-users
/openapi.json get /users
Retrieve a paginated list of users.
**gigstack Connect:** Access other teams' users using the `team` parameter.
# Reset user password
Source: https://docs.gigstack.io/api-reference/users/reset-user-password
/openapi.json post /users/reset-password/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Generate a Firebase password-reset link for the user and email it to them.
The user id goes in the **path** — the only registered route is
`POST /v2/users/reset-password/{id}`. There is no body-based variant, and the request
body is ignored entirely.
**gigstack Connect:** Reset passwords for other teams' users using the `team` parameter.
# Update user
Source: https://docs.gigstack.io/api-reference/users/update-user
/openapi.json put /users/{id}
Update an existing user.
**gigstack Connect:** Update other teams' users using the `team` parameter.
# Create webhook
Source: https://docs.gigstack.io/api-reference/webhooks/create-webhook
/openapi.json post /webhooks
Create a new webhook endpoint to receive event notifications.
The response includes the webhook's signing `secret`. **It is shown only once**, so store it
immediately. It signs only `sat.invoice.synced` and `invoice_batch.completed` deliveries, in the
`X-Gigstack-Signature` header (`sha256=` + hex HMAC-SHA256 of the raw body). Resource events are not
signed with it. There is
no endpoint to reveal or rotate the secret later; if you lose it, delete the webhook and create
a new one.
Webhooks created here send resource events in the `v1` format unless your team's default is
`v2`. Delivery formats, headers and retry behavior are described in the `webhookEvent` callback
below.
**gigstack Connect:** Create webhooks for other teams using the `team` parameter.
# Delete webhook
Source: https://docs.gigstack.io/api-reference/webhooks/delete-webhook
/openapi.json delete /webhooks/{id}
Permanently delete a webhook endpoint.
**gigstack Connect:** Delete webhooks for other teams using the `team` parameter.
# Get webhook
Source: https://docs.gigstack.io/api-reference/webhooks/get-webhook
/openapi.json get /webhooks/{id}
Retrieve details of a specific webhook by ID.
**gigstack Connect:** Access other teams' webhooks using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/api-reference/webhooks/health-check
/openapi.json get /webhooks/health
Liveness probe for the webhooks module. Unauthenticated.
# List webhooks
Source: https://docs.gigstack.io/api-reference/webhooks/list-webhooks
/openapi.json get /webhooks
Retrieve all configured webhooks for your team.
**gigstack Connect:** Access other teams' webhooks using the `team` parameter.
# Update webhook
Source: https://docs.gigstack.io/api-reference/webhooks/update-webhook
/openapi.json put /webhooks/{id}
Update an existing webhook's configuration. All fields are optional.
**gigstack Connect:** Update webhooks for other teams using the `team` parameter.
# Authentication and test mode
Source: https://docs.gigstack.io/authentication
Choose the right key and team before making requests.
Create an API key in [gigstack settings](https://app.gigstack.pro/settings?tab=api).
Send the literal `Bearer ` prefix followed by the key:
```http theme={null}
Authorization: Bearer YOUR_API_KEY
```
Keep keys on your server. Do not put them in browser JavaScript, public repositories,
screenshots, or prompts shared with other people.
## Live and test data
Both modes use `https://api.gigstack.io/v2`. The key determines the mode; a request-body
`livemode` field does not turn a live API key into a test key.
Lists and searches return data for the key's mode. Operations across modes can be rejected.
Some operations, including creating teams and running end-of-month invoicing, require live keys.
See [API fundamentals](/guides/welcome#test-mode) for the current limitations.
## Working with multiple teams
A gigstack Connect master team can use `?team=TEAM_ID` for eligible teams sharing its billing
account. This requires the `multipleIssuerAccounts` feature. OAuth access tokens are bound
to one team and cannot use this parameter to switch teams.
Follow [gigstack Connect](/guides/gigstack-connect) for setup and errors.
## Authentication errors
Authentication failures can return a plain `{message, error, error_description}` object,
rather than the standard API error envelope. Check the HTTP status first and preserve the
response body when diagnosing a failed call.
Account signup has a separate authentication mechanism. See its API reference entry;
a regular bearer key does not replace the internal signup credential.
# Cancellation Motives Catalog (Motivos de Cancelación)
Source: https://docs.gigstack.io/guides/catalogs/cancellation_motives
Integration guide for Cancellation Motives Catalog (Motivos de Cancelación)
## Overview
The Cancellation Motives catalog defines the standardized codes that specify the reason for cancelling a CFDI document. These codes are essential for maintaining proper audit trails and tax compliance when documents need to be invalidated. The selected motive determines whether a substitution document is required and affects the cancellation process workflow.
## What We See
This catalog contains 4 cancellation motive codes that cover the main scenarios where invoices need to be cancelled:
* **Error-based cancellations** (01, 02): Documents with errors, with or without substitution
* **Transaction-based cancellations** (03): Operations that didn't occur
* **Administrative cancellations** (04): Global invoice adjustments for specific customers
Each motive has specific requirements regarding substitution documents and affects the legal and tax implications of the cancellation.
## How to Use It
When cancelling a CFDI, select the motive code that accurately describes the reason for cancellation. This choice determines whether you need to provide a substitution document and affects the cancellation approval process with SAT.
### Usage Guidelines
* **Accurate classification**: Choose the motive that precisely describes the cancellation reason
* **Substitution requirements**: Provide substitution UUIDs when required (motive 01)
* **Timing considerations**: Cancel documents as soon as possible after identifying issues
* **Customer communication**: Inform customers about cancellations and provide substitutions when applicable
## Cancellation Motives Table
| Code | Description (English) | Descripción (Español) | Substitution Required | Common Scenarios |
| - | - | - | - | - |
| `01` | Document issued with errors with relationship | Comprobante emitido con errores con relación | ✅ Yes | Incorrect amounts, wrong customer data, tax errors |
| `02` | Document issued with errors without relationship | Comprobante emitido con errores sin relación | ❌ No | Data entry errors, no replacement needed |
| `03` | Operation did not take place | No se llevó a cabo la operación | ❌ No | Cancelled sales, failed transactions |
| `04` | Nominative operation related to global invoice | Operación nominativa relacionada en la factura global | ❌ No | Customer requests specific invoice from global billing |
## Detailed Motive Explanations
### Motive 01: Document with Errors (With Relationship)
**Use when:** The invoice contains errors and you need to issue a corrected version.
**Requirements:**
* Must provide UUID of the substitution document
* Substitution document must be issued before cancellation
* Substitution document must use relationship type "04"
**Common scenarios:**
* Wrong customer tax information
* Incorrect product quantities or prices
* Wrong tax calculations
* Incorrect CFDI usage codes
**Process:**
1. Issue substitution document with relationship "04"
2. Cancel original document with motive "01"
3. Provide substitution UUID in cancellation request
### Motive 02: Document with Errors (Without Relationship)
**Use when:** The invoice contains errors but no replacement is needed.
**Requirements:**
* No substitution document required
* Cancellation can be processed immediately
**Common scenarios:**
* Duplicate invoices
* Test invoices issued by mistake
* Wrong customer selected (no substitute needed)
* Data entry errors where transaction is void
**Process:**
1. Cancel document with motive "02"
2. No additional documents required
### Motive 03: Operation Did Not Take Place
**Use when:** The sale or transaction was never completed.
**Requirements:**
* No substitution document required
* Often used for cancelled orders or failed payments
**Common scenarios:**
* Customer cancelled order after invoice was issued
* Payment was declined or failed
* Product was out of stock after invoicing
* Service could not be provided
**Process:**
1. Cancel document with motive "03"
2. Process any necessary refunds
3. Update inventory if applicable
### Motive 04: Global Invoice Customer Request
**Use when:** Converting a global invoice transaction to a specific customer invoice.
**Requirements:**
* Typically used with global invoices (public sales)
* Customer requests personalized invoice
* No substitution required
**Common scenarios:**
* Retail customer wants business invoice for expenses
* Company employee needs receipt for reimbursement
* Customer needs invoice with specific tax information
**Process:**
1. Issue new invoice with customer details
2. Cancel portion of global invoice with motive "04"
3. Provide new invoice to customer
## Implementation Examples
### API Request Examples
#### Motive 01 - With Substitution
```json theme={null}
{
"cancellation": {
"invoice_uuid": "12345678-1234-1234-1234-123456789012",
"motive": "01",
"substitution_uuid": "87654321-4321-4321-4321-210987654321",
"reason": "Incorrect customer tax regime, substitution issued"
}
}
```
#### Motive 02 - Without Substitution
```json theme={null}
{
"cancellation": {
"invoice_uuid": "11111111-2222-3333-4444-555555555555",
"motive": "02",
"reason": "Duplicate invoice issued by error"
}
}
```
#### Motive 03 - Operation Not Completed
```json theme={null}
{
"cancellation": {
"invoice_uuid": "99999999-8888-7777-6666-555555555555",
"motive": "03",
"reason": "Customer cancelled order, payment was declined"
}
}
```
#### Motive 04 - Global Invoice Conversion
```json theme={null}
{
"cancellation": {
"invoice_uuid": "22222222-3333-4444-5555-666666666666",
"motive": "04",
"reason": "Customer requested personalized invoice from global billing",
"new_invoice_uuid": "33333333-4444-5555-6666-777777777777"
}
}
```
### Validation Logic
```javascript theme={null}
const validCancellationMotives = ['01', '02', '03', '04']
const motiveRequirements = {
'01': { substitution_required: true, description: 'Error with relationship' },
'02': { substitution_required: false, description: 'Error without relationship' },
'03': { substitution_required: false, description: 'Operation not completed' },
'04': { substitution_required: false, description: 'Global invoice conversion' },
}
function validateCancellationMotive(motive) {
return validCancellationMotives.includes(motive)
}
function validateCancellationRequest(request) {
const { motive, substitution_uuid, invoice_uuid } = request
// Basic motive validation
if (!validateCancellationMotive(motive)) {
throw new Error(`Invalid cancellation motive: ${motive}`)
}
// UUID validation
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
if (!uuidRegex.test(invoice_uuid)) {
throw new Error('Invalid invoice UUID format')
}
// Motive-specific validation
const requirements = motiveRequirements[motive]
if (requirements.substitution_required) {
if (!substitution_uuid) {
throw new Error(`Motive ${motive} requires substitution UUID`)
}
if (!uuidRegex.test(substitution_uuid)) {
throw new Error('Invalid substitution UUID format')
}
} else {
if (substitution_uuid) {
throw new Error(`Motive ${motive} should not include substitution UUID`)
}
}
return true
}
function getCancellationWorkflow(motive) {
const workflows = {
'01': [
'Issue substitution document with relationship 04',
'Cancel original document with motive 01',
'Provide substitution UUID',
],
'02': ['Cancel document with motive 02', 'No additional documents required'],
'03': ['Cancel document with motive 03', 'Process refunds if applicable', 'Update inventory if needed'],
'04': [
'Issue new personalized invoice',
'Cancel global invoice portion with motive 04',
'Provide new invoice to customer',
],
}
return workflows[motive] || []
}
```
### Business Logic Examples
```javascript theme={null}
function createSubstitutionCancellation(originalInvoice, substitutionInvoice, reason) {
return {
invoice_uuid: originalInvoice.uuid,
motive: '01',
substitution_uuid: substitutionInvoice.uuid,
reason: reason,
timestamp: new Date().toISOString(),
workflow: [
`Substitution issued: ${substitutionInvoice.uuid}`,
`Original cancelled: ${originalInvoice.uuid}`,
'Customer notified of correction',
],
}
}
function createSimpleCancellation(invoice, motive, reason) {
if (motive === '01') {
throw new Error('Use createSubstitutionCancellation for motive 01')
}
return {
invoice_uuid: invoice.uuid,
motive: motive,
reason: reason,
timestamp: new Date().toISOString(),
refund_required: motive === '03',
inventory_update: motive === '03',
}
}
function processGlobalInvoiceConversion(globalInvoice, customer, specificItems) {
const personalizedInvoice = {
customer: customer,
items: specificItems,
relationship_type: null, // No relationship needed
total: specificItems.reduce((sum, item) => sum + item.amount, 0),
}
const cancellation = {
invoice_uuid: globalInvoice.uuid,
motive: '04',
reason: `Customer ${customer.name} requested personalized invoice`,
new_invoice_uuid: personalizedInvoice.uuid,
}
return { personalizedInvoice, cancellation }
}
```
## Business Scenarios and Decision Matrix
### E-commerce Error Correction
```javascript theme={null}
const errorScenarios = {
wrongCustomerInfo: {
motive: '01',
action: 'Issue substitution with correct customer data',
substitution_required: true,
},
duplicateOrder: {
motive: '02',
action: 'Cancel duplicate, keep original',
substitution_required: false,
},
paymentFailed: {
motive: '03',
action: 'Cancel and process refund',
substitution_required: false,
},
}
```
### Retail Global Invoice Management
```javascript theme={null}
const globalInvoiceScenario = {
situation: 'Customer paid cash but now wants company invoice',
original: 'Global invoice for general public',
solution: {
step1: 'Issue new invoice with company details',
step2: 'Cancel global invoice portion with motive 04',
step3: 'Provide company invoice to customer',
},
motive: '04',
}
```
### B2B Transaction Corrections
```javascript theme={null}
const b2bCorrections = {
priceError: {
scenario: 'Wrong pricing on contract',
motive: '01',
process: 'Issue credit note or substitution invoice',
},
contractCancellation: {
scenario: 'Customer cancelled before delivery',
motive: '03',
process: 'Cancel invoice and reverse inventory allocation',
},
}
```
## Important Considerations
### Tax and Legal Implications
1. **Motive 01**: Original document is replaced, tax obligations transfer to substitution
2. **Motive 02**: Original document is void, no tax implications remain
3. **Motive 03**: Transaction never occurred, full tax reversal
4. **Motive 04**: Partial cancellation, remaining global invoice stays valid
### SAT Approval Process
* **Automatic approval**: Motives 02, 03, 04 typically approved automatically
* **Customer acceptance**: Motive 01 may require customer acceptance for substitution
* **Time limits**: Cancellations must be requested within specific timeframes
* **Documentation**: Maintain clear records of cancellation reasons
### Customer Impact
1. **Motive 01**: Customer receives corrected document
2. **Motive 02**: Customer notified of cancellation, no replacement
3. **Motive 03**: Customer receives refund notification
4. **Motive 04**: Customer receives personalized invoice
## Error Prevention
### Common Mistakes
* Using motive 01 without providing substitution UUID
* Using motive 02 for errors that require correction (should use 01)
* Cancelling with motive 03 when transaction actually occurred
* Wrong timing in cancellation requests
### Best Practices
* Cancel documents as soon as errors are identified
* Always issue substitution before cancelling with motive 01
* Maintain clear documentation of cancellation reasons
* Implement automated validation for motive requirements
* Train staff on proper motive selection
## Cancellation Workflow Examples
### Error Correction Workflow (Motive 01)
```
1. Identify error in original invoice
2. Create substitution invoice with relationship "04"
3. Submit cancellation request with motive "01"
4. Include substitution UUID in request
5. Wait for SAT approval
6. Notify customer of correction
```
### Simple Cancellation Workflow (Motive 02/03)
```
1. Identify need for cancellation
2. Submit cancellation request with appropriate motive
3. Wait for SAT approval (usually automatic)
4. Process refunds if applicable (motive 03)
5. Update internal systems
```
### Global Invoice Conversion Workflow (Motive 04)
```
1. Customer requests personalized invoice
2. Create new invoice with customer details
3. Submit cancellation of global invoice portion with motive "04"
4. Provide new invoice to customer
5. Update accounting records
```
## Related Documentation
* [Invoice Relationships Catalog](/guides/catalogs/invoice_relationships) - For substitution document relationships (code 04)
* [Months and Bimesters Catalog](/guides/catalogs/months_and_bimesters) - For global invoice periodicity
* [Payment Methods Catalog](/guides/catalogs/payment_methods) - For payment-related cancellations
* [Tax Regimes Catalog](/guides/catalogs/tax_regimes) - For tax regime correction scenarios
# CFDI Errors Reference
Source: https://docs.gigstack.io/guides/catalogs/cfdi_errors
Integration guide for CFDI Errors Reference
## Overview
The CFDI Errors Reference provides a comprehensive catalog of error codes that may occur during the invoice creation and stamping process. This reference helps developers understand, diagnose, and resolve issues that arise when working with CFDI 4.0 invoices and SAT integration.
## What We See
This catalog contains detailed information about CFDI errors, including error codes, descriptions, explanations of why errors occur, and actionable solutions. Each error is categorized by type to help you quickly identify whether the issue relates to the invoice structure, receiver information, sender configuration, or other factors.
## How to Use It
Use the CFDI Errors endpoint to search for specific error codes or browse available errors by type. This is particularly useful when you receive error responses during invoice creation or when implementing error handling in your application.
### Usage Guidelines
* **Search by code**: Use the exact error code (e.g., CFDI140223) to get specific error details
* **Filter by type**: Narrow down errors by category (invoice, receiver, sender, unknown)
* **Full-text search**: Search across all fields using the `q` parameter
* **Implement error handling**: Use this reference to provide meaningful error messages to your users
## API Endpoint
### List CFDI Errors
```http theme={null}
GET /invoices/errors
```
Retrieve a paginated and filterable list of CFDI errors from the error matrix.
**Query Parameters:**
* `code` (string, optional) - Filter by exact error code. Returns a single document if found.
* `q` (string, optional) - Search query. Searches across code, description, explanation, and solution fields.
* `type` (string, optional) - Filter by error type. Valid values: `invoice`, `receiver`, `sender`, `unknown`
* `limit` (number, optional) - Number of results per page (default: 50, max: 100)
* `page` (number, optional) - Page number for pagination (default: 1)
**Example Request - Get Specific Error:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/errors?code=CFDI140223" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"data": [
{
"code": "CFDI140223",
"description": "El campo Rfc del receptor no es valido",
"explanation": "The RFC (tax ID) provided for the receiver does not meet the validation requirements or format specified by SAT",
"solution": "Verify that the receiver's RFC is correct, properly formatted (13 characters for individuals, 12 for legal entities), and matches SAT's registered information",
"type": "receiver"
}
],
"total": 1,
"page": 1,
"limit": 1,
"message": "CFDI error retrieved successfully",
"timestamp": "2025-12-19T10:30:00.000Z"
}
```
**Example Request - Search Errors:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/errors?q=RFC&type=receiver&limit=10&page=1" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"data": [
{
"code": "CFDI140223",
"description": "El campo Rfc del receptor no es valido",
"explanation": "The RFC (tax ID) provided for the receiver does not meet the validation requirements",
"solution": "Verify that the receiver's RFC is correct and properly formatted",
"type": "receiver"
},
{
"code": "CFDI140225",
"description": "El RFC del receptor no existe en el padron del SAT",
"explanation": "The receiver's RFC is not registered in SAT's taxpayer registry",
"solution": "Confirm the RFC is registered with SAT or contact the receiver to verify their tax information",
"type": "receiver"
}
],
"total": 2,
"page": 1,
"limit": 10,
"message": "CFDI errors retrieved successfully",
"timestamp": "2025-12-19T10:30:00.000Z"
}
```
**Example Request - Filter by Type:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/errors?type=invoice&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Request - Browse All Errors:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/errors?limit=50&page=1" \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Error Types
The CFDI errors are categorized into the following types:
### invoice
Errors related to the invoice structure, fields, totals, taxes, or general CFDI document configuration.
**Common scenarios:**
* Invalid tax calculations
* Missing required fields
* Incorrect CFDI relationships
* Invalid payment terms or methods
### receiver
Errors related to the invoice receiver (client) information.
**Common scenarios:**
* Invalid or unregistered RFC
* Incorrect tax regime
* Missing required receiver fields
* Invalid CFDI usage code for receiver
### sender
Errors related to the invoice sender (issuer) configuration and credentials.
**Common scenarios:**
* Invalid CSD (Digital Stamp Certificate)
* Expired certificates
* Incorrect RFC configuration
* Missing sender credentials
### unknown
Errors that don't fit into the other categories or have undetermined causes.
## Response Schema
### Success Response
```json theme={null}
{
"success": true,
"data": [
{
"code": "string",
"description": "string",
"explanation": "string",
"solution": "string",
"type": "string"
}
],
"total": "number",
"page": "number",
"limit": "number",
"message": "string",
"timestamp": "string"
}
```
**Field Descriptions:**
* `code` - The unique error code identifier (e.g., CFDI140223)
* `description` - Brief description of the error, typically in Spanish as provided by SAT
* `explanation` - Detailed explanation of why this error occurs
* `solution` - Actionable steps to resolve the error
* `type` - Category of the error: `invoice`, `receiver`, `sender`, or `unknown`
### Error Responses
**404 Not Found** - When filtering by code and the error code doesn't exist:
```json theme={null}
{
"success": false,
"message": "CFDI Error not found",
"error": "Error code 'INVALID_CODE' not found",
"timestamp": "2025-12-19T10:30:00.000Z"
}
```
**500 Internal Server Error** - When a server error occurs:
```json theme={null}
{
"success": false,
"message": "Internal server error",
"error": "An error occurred while retrieving CFDI errors",
"timestamp": "2025-12-19T10:30:00.000Z"
}
```
## Common Use Cases
### 1. Implementing Error Messages
When you receive an error code from the invoice creation endpoint, use this reference to provide meaningful feedback:
```javascript theme={null}
async function handleInvoiceError(errorCode) {
const response = await fetch(
`https://api.gigstack.io/v2/invoices/errors?code=${errorCode}`,
{
headers: {
Authorization: `Bearer ${YOUR_TOKEN}`
}
}
);
const { data } = await response.json();
const error = data[0];
// Display user-friendly error message
console.log(`Error: ${error.description}`);
console.log(`What happened: ${error.explanation}`);
console.log(`How to fix: ${error.solution}`);
}
```
### 2. Building Error Documentation
Create a searchable error reference page in your application:
```javascript theme={null}
async function searchErrors(searchTerm) {
const response = await fetch(
`https://api.gigstack.io/v2/invoices/errors?q=${encodeURIComponent(searchTerm)}&limit=50`,
{
headers: {
Authorization: `Bearer ${YOUR_TOKEN}`
}
}
);
return await response.json();
}
```
### 3. Categorizing Errors by Type
Filter errors by type to help users troubleshoot specific issues:
```javascript theme={null}
async function getReceiverErrors() {
const response = await fetch(
'https://api.gigstack.io/v2/invoices/errors?type=receiver&limit=100',
{
headers: {
Authorization: `Bearer ${YOUR_TOKEN}`
}
}
);
return await response.json();
}
```
### 4. Pagination for Large Result Sets
When browsing all errors or large search results:
```javascript theme={null}
async function getAllErrors(page = 1) {
const response = await fetch(
`https://api.gigstack.io/v2/invoices/errors?limit=50&page=${page}`,
{
headers: {
Authorization: `Bearer ${YOUR_TOKEN}`
}
}
);
const result = await response.json();
const totalPages = Math.ceil(result.total / result.limit);
return {
...result,
totalPages,
hasNextPage: page < totalPages
};
}
```
## Best Practices
1. **Cache error data**: The error matrix is relatively static, so consider caching the results locally to reduce API calls
2. **Implement search**: Build a search interface for your users to quickly find error solutions
3. **Categorize errors**: Use the `type` field to help users identify the source of issues
4. **Provide context**: When displaying errors, include both the description and solution for better user experience
5. **Handle 404s gracefully**: If an error code is not found, provide fallback messaging or contact support
6. **Use pagination**: When retrieving all errors, use pagination to avoid large response payloads
## Integration Example
Here's a complete example of integrating CFDI error handling into your invoice workflow:
```javascript theme={null}
class InvoiceErrorHandler {
constructor(apiToken) {
this.apiToken = apiToken;
this.baseUrl = 'https://api.gigstack.io/v2';
}
async getErrorDetails(errorCode) {
try {
const response = await fetch(
`${this.baseUrl}/invoices/errors?code=${errorCode}`,
{
headers: {
Authorization: `Bearer ${this.apiToken}`
}
}
);
if (response.status === 404) {
return {
code: errorCode,
description: 'Unknown error',
explanation: 'This error code is not in our reference database',
solution: 'Please contact support with this error code',
type: 'unknown'
};
}
const result = await response.json();
return result.data[0];
} catch (error) {
console.error('Failed to fetch error details:', error);
return null;
}
}
async searchSolutions(searchTerm, errorType = null) {
const params = new URLSearchParams({
q: searchTerm,
limit: 20
});
if (errorType) {
params.append('type', errorType);
}
const response = await fetch(
`${this.baseUrl}/invoices/errors?${params}`,
{
headers: {
Authorization: `Bearer ${this.apiToken}`
}
}
);
return await response.json();
}
formatErrorMessage(error) {
return `
Error Code: ${error.code}
Type: ${error.type}
${error.description}
Why this happened:
${error.explanation}
How to fix it:
${error.solution}
`.trim();
}
}
// Usage
const errorHandler = new InvoiceErrorHandler('your_api_token');
// When invoice creation fails with an error code
const errorDetails = await errorHandler.getErrorDetails('CFDI140223');
console.log(errorHandler.formatErrorMessage(errorDetails));
// Search for related errors
const relatedErrors = await errorHandler.searchSolutions('RFC', 'receiver');
```
## Important Notes
1. **Read-only endpoint**: This endpoint only retrieves error information and does not modify any data
2. **No authentication for error browsing**: While authentication is required, this endpoint doesn't access team-specific data
3. **Static data**: The CFDI error matrix is periodically updated but relatively stable
4. **Spanish descriptions**: Most error descriptions are in Spanish as they come from SAT documentation
5. **Search is case-insensitive**: The `q` parameter performs case-insensitive searches across all text fields
## Related Documentation
* [Invoices API](/guides/invoices) - Create and manage invoices
* [Clients API](/guides/clients) - Manage invoice receivers
* [Teams API](/guides/teams) - Configure sender credentials
## Support
If you encounter an error code not in this reference or need additional assistance:
* Check the [support documentation](https://docs.gigstack.io)
* Contact support at [support@gigstack.io](mailto:support@gigstack.io)
* Review SAT's official CFDI documentation
***
**Note**: Error codes and descriptions are based on SAT's CFDI 4.0 specifications and are subject to updates as regulations change.
# Invoice Relationships Catalog (Relación entre Facturas)
Source: https://docs.gigstack.io/guides/catalogs/invoice_relationships
Integration guide for Invoice Relationships Catalog (Relación entre Facturas)
## Overview
The Invoice Relationships catalog defines the standardized codes that specify how CFDI documents relate to each other. These relationships are crucial for maintaining proper audit trails, tax compliance, and business process documentation. When issuing a CFDI that modifies, references, or relates to previous documents, the appropriate relationship code must be specified along with the UUID(s) of the related document(s).
## What We See
This catalog contains 9 relationship codes organized into four main categories:
* **Adjustment Documents** (01, 02): Credit and debit notes
* **Substitution and Returns** (03, 04): Document replacements and merchandise returns
* **Goods Movement** (05, 06): Transfer and billing relationships
* **Payment-Related** (07, 08, 09): Advance applications and payment processing
Each relationship code establishes a formal connection between documents that must be maintained for tax and legal compliance.
## How to Use It
When creating a CFDI that relates to previous documents, select the appropriate relationship code that accurately describes the business relationship. Always include the UUID(s) of the related documents to maintain the audit trail required by SAT regulations.
### Usage Guidelines
* **Accurate classification**: Choose the code that precisely describes the document relationship
* **Complete references**: Always include UUIDs of all related documents
* **Maintain chronology**: Ensure related documents exist and are properly sequenced
* **Compliance validation**: Verify that relationships comply with SAT regulations
## Relationship Types and Codes
### Adjustment Documents
| Code | Description (English) | Descripción (Español) | Usage Context | Impact |
| - | - | - | - | - |
| `01` | Credit note for related documents | Nota de crédito de los documentos relacionados | Refunds, discounts, returns | Reduces tax liability |
| `02` | Debit note for related documents | Nota de débito de los documentos relacionados | Additional charges, corrections | Increases tax liability |
### Substitution and Returns
| Code | Description (English) | Descripción (Español) | Usage Context | Impact |
| - | - | - | - | - |
| `03` | Merchandise return on previous invoices or transfers | Devolución de mercancía sobre facturas o traslados previos | Product returns, exchanges | Reverses original transaction |
| `04` | Substitution of previous CFDI | Sustitución de los CFDI previos | Document corrections, replacements | Replaces original document |
### Goods Movement
| Code | Description (English) | Descripción (Español) | Usage Context | Impact |
| - | - | - | - | - |
| `05` | Transfer of previously invoiced merchandise | Traslados de mercancias facturados previamente | Warehouse transfers, relocations | Tracks inventory movement |
| `06` | Invoice generated from previous transfers | Factura generada por los traslados previos | Converting transfers to sales | Creates billing from movement |
### Payment-Related
| Code | Description (English) | Descripción (Español) | Usage Context | Impact |
| - | - | - | - | - |
| `07` | CFDI for advance application | CFDI por aplicación de anticipo | Applying customer deposits | Applies prepaid amounts |
| `08` | Invoice generated from installment payments | Factura generada por pagos en parcialidades | PPD payment processing | Documents payment receipt |
| `09` | Invoice generated from deferred payments | Factura generada por pagos diferidos | Delayed payment processing | Documents delayed payment |
## Complete Relationships Table
| Code | Description (English) | Descripción (Español) | Category | Business Scenario |
| - | - | - | - | - |
| `01` | Credit note for related documents | Nota de crédito de los documentos relacionados | Adjustment | Customer refund, price adjustment downward |
| `02` | Debit note for related documents | Nota de débito de los documentos relacionados | Adjustment | Additional charges, price adjustment upward |
| `03` | Merchandise return on previous invoices or transfers | Devolución de mercancía sobre facturas o traslados previos | Return | Product return, defective merchandise |
| `04` | Substitution of previous CFDI | Sustitución de los CFDI previos | Substitution | Correcting errors, document replacement |
| `05` | Transfer of previously invoiced merchandise | Traslados de mercancias facturados previamente | Movement | Warehouse transfer, location change |
| `06` | Invoice generated from previous transfers | Factura generada por los traslados previos | Billing | Converting transfer to sale |
| `07` | CFDI for advance application | CFDI por aplicación de anticipo | Payment | Applying customer deposit |
| `08` | Invoice generated from installment payments | Factura generada por pagos en parcialidades | Payment | PPD payment complement |
| `09` | Invoice generated from deferred payments | Factura generada por pagos diferidos | Payment | Deferred payment processing |
## Implementation Examples
### API Request Examples
#### Credit Note (01)
```json theme={null}
{
"credit_note": {
"type": "I",
"relationship_type": "01",
"related_documents": [
{
"uuid": "12345678-1234-1234-1234-123456789012",
"tax_id": "RFC123456789",
"partial_amount": 1160.0
}
],
"reason": "Customer return - defective product",
"total": -1160.0
}
}
```
#### Document Substitution (04)
```json theme={null}
{
"invoice": {
"relationship_type": "04",
"related_documents": [
{
"uuid": "87654321-4321-4321-4321-210987654321",
"tax_id": "RFC123456789"
}
],
"substitution_reason": "Correction of customer tax regime",
"total": 2500.0
}
}
```
#### Advance Application (07)
```json theme={null}
{
"advance_application": {
"relationship_type": "07",
"related_documents": [
{
"uuid": "11111111-2222-3333-4444-555555555555",
"tax_id": "RFC123456789",
"applied_amount": 5000.0
}
],
"remaining_balance": 15000.0,
"total": 20000.0
}
}
```
### Validation Logic
```javascript theme={null}
const validRelationshipTypes = ['01', '02', '03', '04', '05', '06', '07', '08', '09']
const relationshipCategories = {
adjustment: ['01', '02'],
return_substitution: ['03', '04'],
goods_movement: ['05', '06'],
payment_related: ['07', '08', '09'],
}
function validateRelationshipType(type) {
return validRelationshipTypes.includes(type)
}
function getRelationshipCategory(type) {
for (const [category, types] of Object.entries(relationshipCategories)) {
if (types.includes(type)) return category
}
return 'unknown'
}
function validateRelatedDocuments(relationshipType, relatedDocs) {
// Basic validation
if (!relatedDocs || relatedDocs.length === 0) {
throw new Error('Related documents are required')
}
// Validate UUID format for each related document
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
for (const doc of relatedDocs) {
if (!uuidRegex.test(doc.uuid)) {
throw new Error(`Invalid UUID format: ${doc.uuid}`)
}
if (!doc.tax_id) {
throw new Error('Tax ID is required for related documents')
}
// Validate specific requirements by relationship type
if (['01', '07'].includes(relationshipType) && !doc.partial_amount) {
throw new Error(`Partial amount required for relationship type ${relationshipType}`)
}
}
return true
}
```
### Business Logic Examples
```javascript theme={null}
function createCreditNote(originalInvoice, returnAmount, reason) {
return {
type: 'E', // Egreso (Credit Note)
relationship_type: '01',
related_documents: [
{
uuid: originalInvoice.uuid,
tax_id: originalInvoice.issuer.tax_id,
partial_amount: returnAmount,
},
],
customer: originalInvoice.customer,
items: originalInvoice.items.map((item) => ({
...item,
quantity: -item.quantity, // Negative quantities for credit
amount: -item.amount,
})),
reason: reason,
total: -returnAmount,
}
}
function createSubstitutionInvoice(originalInvoice, corrections) {
return {
type: 'I', // Ingreso (Invoice)
relationship_type: '04',
related_documents: [
{
uuid: originalInvoice.uuid,
tax_id: originalInvoice.issuer.tax_id,
},
],
...corrections, // Apply corrections
substitution_reason: corrections.reason,
}
}
function applyAdvancePayment(advanceInvoice, finalInvoice) {
return {
type: 'I',
relationship_type: '07',
related_documents: [
{
uuid: advanceInvoice.uuid,
tax_id: advanceInvoice.issuer.tax_id,
applied_amount: advanceInvoice.total,
},
],
customer: finalInvoice.customer,
items: finalInvoice.items,
subtotal: finalInvoice.subtotal,
advance_applied: advanceInvoice.total,
total: finalInvoice.total - advanceInvoice.total,
}
}
```
## Common Business Scenarios
### E-commerce Returns Process
```javascript theme={null}
// Customer returns a defective product
const returnProcess = {
step1: 'Customer initiates return',
step2: 'Verify original invoice UUID',
step3: 'Create credit note with relationship 01',
step4: 'Process refund to customer',
implementation: {
relationship_type: '01',
related_documents: [{ uuid: 'original-invoice-uuid', partial_amount: 299.99 }],
reason: 'Defective product return',
},
}
```
### B2B Advance Payment Application
```javascript theme={null}
// Applying customer deposit to final invoice
const advanceApplication = {
scenario: 'Customer paid 50% advance, now paying remaining 50%',
relationship_type: '07',
advance_invoice_uuid: 'advance-payment-uuid',
applied_amount: 25000.0,
final_invoice_total: 50000.0,
customer_owes: 25000.0,
}
```
### Document Correction Process
```javascript theme={null}
// Correcting customer information on invoice
const documentCorrection = {
error: 'Wrong customer tax regime on original invoice',
solution: 'Issue substitution invoice with correct information',
relationship_type: '04',
original_uuid: 'incorrect-invoice-uuid',
correction: 'Updated customer tax regime from 605 to 612',
}
```
## Document Chain Examples
### Credit Note Chain
```
Original Invoice (UUID: A123) → Credit Note (01, relates to A123) → Customer Refund
```
### Substitution Chain
```
Incorrect Invoice (UUID: B456) → Substitution Invoice (04, relates to B456) → Corrected Document
```
### Advance Payment Chain
```
Advance Payment (UUID: C789) → Final Invoice (07, relates to C789) → Balance Due
```
### Transfer to Sale Chain
```
Transfer Document (UUID: D012) → Sales Invoice (06, relates to D012) → Customer Billing
```
## Important Considerations
### Tax Implications
1. **Credit Notes (01)**: Reduce tax liability for the issuer
2. **Debit Notes (02)**: Increase tax liability for the issuer
3. **Substitutions (04)**: Replace original tax obligations
4. **Returns (03)**: May affect inventory and tax deductions
### Compliance Requirements
1. **UUID References**: All related documents must include valid UUIDs
2. **Tax ID Matching**: Related documents must include correct tax IDs
3. **Chronological Order**: Relationships must respect document chronology
4. **Amount Validation**: Partial amounts cannot exceed original totals
### System Requirements
1. **Document Tracking**: Maintain complete audit trails
2. **Validation Logic**: Implement relationship-specific validations
3. **Status Updates**: Update original document status when related
4. **Reporting**: Generate relationship reports for compliance
## Error Prevention
### Common Mistakes
* Using wrong relationship codes for business scenarios
* Missing or invalid UUID references
* Incorrect partial amounts in credit notes
* Creating circular references between documents
* Forgetting to update original document status
### Best Practices
* Implement comprehensive validation rules
* Maintain clear documentation of business processes
* Train staff on proper relationship code usage
* Regular audit of document relationships
* Automated validation of UUID references
## Related Documentation
* [Payment Methods Catalog](/guides/catalogs/payment_methods) - For payment-related relationships (07, 08, 09)
* [CFDI Usage Catalog](/guides/catalogs/usages) - For compatible usage codes
* [Invoice Globals](/guides/catalogs/invoices_globals) - For general invoice configuration
* [Tax Regimes Catalog](/guides/catalogs/tax_regimes) - For tax regime considerations
# Global Invoices (Factura Global)
Source: https://docs.gigstack.io/guides/catalogs/invoices_globals
Integration guide for Global Invoices (Factura Global)
## Overview
A **global invoice** is a single CFDI that summarizes sales made to the general public (*público en general*) — customers who did not ask for an invoice of their own — over a period: a day, a week, a fortnight, a month or two months.
In gigstack you issue one by adding a `global` object to an income invoice (`POST /v2/invoices/income`, or a draft with `POST /v2/invoices/draft`). gigstack can also issue them automatically at the end of each month from your pending receipts; see [End-of-Month Global Invoicing](/guides/invoices#end-of-month-global-invoicing).
## The `global` Object
| Field | Type | Required | Description |
| - | - | - | - |
| `periodicity` | string | yes | SAT `c_Periodicidad` code — how often you issue global invoices (see below) |
| `months` | string | yes | SAT `c_Meses` code — the month or bimester covered (see [Months and Bimesters](/guides/catalogs/months_and_bimesters)) |
| `year` | integer | yes | Four-digit year covered, e.g. `2026` |
gigstack passes these three values to the stamping provider as sent; it does not check them against each other. An invalid combination is rejected at stamping time by the SAT's validation, so check them before you send.
## Periodicity Codes (`c_Periodicidad`)
| Code | Periodicity | Descripción |
| - | - | - |
| `01` | Daily | Diario |
| `02` | Weekly | Semanal |
| `03` | Fortnightly | Quincenal |
| `04` | Monthly | Mensual |
| `05` | Bimonthly | Bimestral |
Use `months` `01`–`12` with periodicities `01`–`04`, and a bimester code `13`–`18` with periodicity `05`.
## The Receiver
A global invoice is addressed to the general public. These are the SAT's rules for the receiver of a global invoice:
| Field | Value |
| - | - |
| RFC (`tax_id`) | `XAXX010101000` |
| Name (`legal_name`) | `PUBLICO EN GENERAL` |
| Tax regime (`tax_system`) | `616` — Sin obligaciones fiscales |
| CFDI use (`use`) | `S01` — Sin efectos fiscales |
| Postal code | The issuer's postal code (lugar de expedición) |
gigstack's built-in general-public client uses `PUBLICO EN GENERAL`, `XAXX010101000` and regime `616`.
## Example
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/income \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"automation_type": "none",
"client": {
"legal_name": "PUBLICO EN GENERAL",
"tax_id": "XAXX010101000",
"tax_system": "616",
"address": { "zip": "06600" }
},
"currency": "MXN",
"global": {
"periodicity": "04",
"months": "08",
"year": 2026
},
"items": [{
"description": "Ventas del periodo",
"quantity": 1,
"unit_price": 50000.0,
"product_key": "01010101",
"unit_key": "ACT",
"taxes": [{ "type": "IVA", "rate": 0.16, "factor": "Tasa", "withholding": false }]
}],
"use": "S01",
"payment_form": "01",
"payment_method": "PUE"
}'
```
Here `06600` stands in for **your** (the issuer's) postal code.
## Team Settings That Affect Global Invoices
* `settings.global_invoice_disabled` — turns off the automatic end-of-month global invoice.
* `settings.periodicity` — the team's default periodicity (`day`, `week`, `two_weeks`, `month`, `two_months`).
Both are read and written with `PUT /v2/teams/{id}/settings`.
## Related Resources
* [Months and Bimesters Catalog](/guides/catalogs/months_and_bimesters)
* [CFDI Usage Catalog](/guides/catalogs/usages)
* [Tax Regimes Catalog](/guides/catalogs/tax_regimes)
* [Invoices API](/guides/invoices)
# Months and Bimesters Catalog (Meses y Bimestres)
Source: https://docs.gigstack.io/guides/catalogs/months_and_bimesters
Integration guide for Months and Bimesters Catalog (Meses y Bimestres)
## Overview
The Months and Bimesters catalog defines standardized periodicity codes used in Global Invoices (Factura Global) to specify the reporting period covered by the invoice. These codes are essential for businesses that consolidate multiple transactions into periodic global invoices for tax efficiency and administrative simplification.
## What We See
This catalog contains 18 periodicity codes covering:
* **Individual months** (01-12): Each calendar month from January to December
* **Bimesters** (13-18): Two-month periods covering the entire year in six segments
Global invoices allow businesses to group daily transactions and issue periodic summary invoices, with the periodicity code indicating the time span covered.
## How to Use It
When creating global invoices, select the periodicity code that corresponds to the actual period being covered by the invoice. This ensures proper tax reporting and compliance with SAT regulations for consolidated invoicing.
### Usage Guidelines
* **Match actual period**: Select the code that exactly matches the invoice period
* **Consistent application**: Use the same periodicity consistently for your business
* **Tax period alignment**: Ensure periodicity aligns with your tax filing schedule
* **Complete periods only**: Global invoices should cover complete months or bimesters
## Periodicity Codes Table
### Individual Months (01-12)
| Code | Description (English) | Descripción (Español) | Calendar Period | Quarter |
| - | - | - | - | - |
| `01` | January | Enero | Jan 1 - Jan 31 | Q1 |
| `02` | February | Febrero | Feb 1 - Feb 28/29 | Q1 |
| `03` | March | Marzo | Mar 1 - Mar 31 | Q1 |
| `04` | April | Abril | Apr 1 - Apr 30 | Q2 |
| `05` | May | Mayo | May 1 - May 31 | Q2 |
| `06` | June | Junio | Jun 1 - Jun 30 | Q2 |
| `07` | July | Julio | Jul 1 - Jul 31 | Q3 |
| `08` | August | Agosto | Aug 1 - Aug 31 | Q3 |
| `09` | September | Septiembre | Sep 1 - Sep 30 | Q3 |
| `10` | October | Octubre | Oct 1 - Oct 31 | Q4 |
| `11` | November | Noviembre | Nov 1 - Nov 30 | Q4 |
| `12` | December | Diciembre | Dec 1 - Dec 31 | Q4 |
### Bimesters (13-18)
| Code | Description (English) | Descripción (Español) | Calendar Period | Months Covered |
| - | - | - | - | - |
| `13` | January-February | Enero-Febrero | Jan 1 - Feb 28/29 | 01, 02 |
| `14` | March-April | Marzo-Abril | Mar 1 - Apr 30 | 03, 04 |
| `15` | May-June | Mayo-Junio | May 1 - Jun 30 | 05, 06 |
| `16` | July-August | Julio-Agosto | Jul 1 - Aug 31 | 07, 08 |
| `17` | September-October | Septiembre-Octubre | Sep 1 - Oct 31 | 09, 10 |
| `18` | November-December | Noviembre-Diciembre | Nov 1 - Dec 31 | 11, 12 |
## Global Invoice Context
### What is a Global Invoice?
A Global Invoice (Factura Global) is a CFDI document that consolidates multiple individual transactions from a specific period into a single invoice. This is commonly used by:
* Retail businesses with many daily transactions
* Service providers with recurring billing
* Businesses with high transaction volumes
### When to Use Periodicity Codes
* **Daily consolidation**: Businesses that want to issue monthly summaries instead of individual receipts
* **Subscription services**: Companies billing customers on monthly or bi-monthly cycles
* **Retail operations**: Stores consolidating daily sales into periodic invoices
* **Service providers**: Professional services billing on periodic schedules
## Implementation Examples
### API Request Example
```json theme={null}
{
"global_invoice": {
"periodicity": "01",
"period_year": 2024,
"start_date": "2024-01-01",
"end_date": "2024-01-31",
"total_transactions": 1250,
"total_amount": 87500.0
}
}
```
### Validation Logic
```javascript theme={null}
const validPeriodicities = [
'01',
'02',
'03',
'04',
'05',
'06',
'07',
'08',
'09',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18',
]
const monthlyPeriods = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
const bimesterPeriods = ['13', '14', '15', '16', '17', '18']
function validatePeriodicity(code) {
return validPeriodicities.includes(code)
}
function getPeriodicityType(code) {
if (monthlyPeriods.includes(code)) return 'monthly'
if (bimesterPeriods.includes(code)) return 'bimester'
return 'unknown'
}
function validatePeriodDates(periodicity, startDate, endDate, year) {
const periodMap = {
'01': { start: `${year}-01-01`, end: `${year}-01-31` },
'02': { start: `${year}-02-01`, end: `${year}-02-${isLeapYear(year) ? '29' : '28'}` },
'03': { start: `${year}-03-01`, end: `${year}-03-31` },
'04': { start: `${year}-04-01`, end: `${year}-04-30` },
'05': { start: `${year}-05-01`, end: `${year}-05-31` },
'06': { start: `${year}-06-01`, end: `${year}-06-30` },
'07': { start: `${year}-07-01`, end: `${year}-07-31` },
'08': { start: `${year}-08-01`, end: `${year}-08-31` },
'09': { start: `${year}-09-01`, end: `${year}-09-30` },
10: { start: `${year}-10-01`, end: `${year}-10-31` },
11: { start: `${year}-11-01`, end: `${year}-11-30` },
12: { start: `${year}-12-01`, end: `${year}-12-31` },
13: { start: `${year}-01-01`, end: `${year}-02-${isLeapYear(year) ? '29' : '28'}` },
14: { start: `${year}-03-01`, end: `${year}-04-30` },
15: { start: `${year}-05-01`, end: `${year}-06-30` },
16: { start: `${year}-07-01`, end: `${year}-08-31` },
17: { start: `${year}-09-01`, end: `${year}-10-31` },
18: { start: `${year}-11-01`, end: `${year}-12-31` },
}
const expectedPeriod = periodMap[periodicity]
return expectedPeriod && startDate === expectedPeriod.start && endDate === expectedPeriod.end
}
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
}
```
### Period Calculation Helper
```javascript theme={null}
function calculatePeriodInfo(periodicity, year) {
const periodicityInfo = {
'01': { name: 'January', nameEs: 'Enero', months: [1], quarter: 1 },
'02': { name: 'February', nameEs: 'Febrero', months: [2], quarter: 1 },
'03': { name: 'March', nameEs: 'Marzo', months: [3], quarter: 1 },
'04': { name: 'April', nameEs: 'Abril', months: [4], quarter: 2 },
'05': { name: 'May', nameEs: 'Mayo', months: [5], quarter: 2 },
'06': { name: 'June', nameEs: 'Junio', months: [6], quarter: 2 },
'07': { name: 'July', nameEs: 'Julio', months: [7], quarter: 3 },
'08': { name: 'August', nameEs: 'Agosto', months: [8], quarter: 3 },
'09': { name: 'September', nameEs: 'Septiembre', months: [9], quarter: 3 },
10: { name: 'October', nameEs: 'Octubre', months: [10], quarter: 4 },
11: { name: 'November', nameEs: 'Noviembre', months: [11], quarter: 4 },
12: { name: 'December', nameEs: 'Diciembre', months: [12], quarter: 4 },
13: { name: 'January-February', nameEs: 'Enero-Febrero', months: [1, 2], quarter: '1-1' },
14: { name: 'March-April', nameEs: 'Marzo-Abril', months: [3, 4], quarter: '1-2' },
15: { name: 'May-June', nameEs: 'Mayo-Junio', months: [5, 6], quarter: '2-2' },
16: { name: 'July-August', nameEs: 'Julio-Agosto', months: [7, 8], quarter: '3-3' },
17: { name: 'September-October', nameEs: 'Septiembre-Octubre', months: [9, 10], quarter: '3-4' },
18: { name: 'November-December', nameEs: 'Noviembre-Diciembre', months: [11, 12], quarter: '4-4' },
}
const info = periodicityInfo[periodicity]
if (!info) return null
const startMonth = info.months[0]
const endMonth = info.months[info.months.length - 1]
const startDate = new Date(year, startMonth - 1, 1)
const endDate = new Date(year, endMonth, 0) // Last day of end month
return {
...info,
year,
startDate: startDate.toISOString().split('T')[0],
endDate: endDate.toISOString().split('T')[0],
type: info.months.length === 1 ? 'monthly' : 'bimester',
}
}
```
## Common Use Cases
### Retail Business - Monthly Consolidation
```javascript theme={null}
// Coffee shop consolidating daily sales into monthly invoice
const retailGlobalInvoice = {
business_type: 'retail',
periodicity: '01', // January
period_year: 2024,
daily_transactions: 850,
total_amount: 125000.0,
description: 'Coffee shop monthly sales consolidation',
}
```
### Subscription Service - Bimester Billing
```javascript theme={null}
// Software service billing customers bi-monthly
const subscriptionInvoice = {
business_type: 'subscription',
periodicity: '13', // January-February
period_year: 2024,
subscribers: 2500,
total_amount: 375000.0,
description: 'Software subscription bi-monthly billing',
}
```
### Professional Services - Quarterly Summary
```javascript theme={null}
// Consulting firm using monthly periods for quarterly reporting
const consultingInvoices = [
{ periodicity: '01', amount: 85000.0 }, // January
{ periodicity: '02', amount: 92000.0 }, // February
{ periodicity: '03', amount: 88000.0 }, // March
]
// Q1 total: 265,000.00
```
## Business Considerations
### Choosing Between Monthly and Bimester Periods
#### Monthly Periods (01-12)
**Advantages:**
* More granular reporting
* Easier alignment with accounting cycles
* Better cash flow tracking
* Standard business reporting periods
**Best for:**
* Businesses with seasonal variations
* Companies requiring detailed monthly analysis
* Organizations with monthly tax obligations
#### Bimester Periods (13-18)
**Advantages:**
* Reduced administrative burden
* Lower invoice processing costs
* Simplified compliance for smaller businesses
* Better for stable, recurring revenue
**Best for:**
* Subscription-based businesses
* Service providers with stable billing
* Small businesses seeking simplification
* Companies with consistent bi-monthly cycles
## Important Notes
1. **Period Completeness**: Global invoices must cover complete periods - partial months or bimesters are not allowed
2. **Tax Implications**: The chosen periodicity affects tax reporting schedules and obligations
3. **Customer Communication**: Clearly communicate billing periods to customers using global invoices
4. **Consistency**: Maintain consistent periodicity choices for regulatory compliance
5. **Leap Year Handling**: February periods must account for leap years (29 days vs 28 days)
## Error Prevention
### Common Mistakes
* Using partial periods (e.g., January 15-31 instead of full January)
* Mixing monthly and bimester periods inconsistently
* Incorrect date ranges for chosen periodicity
* Forgetting leap year adjustments for February
### Best Practices
* Implement automated period calculation
* Validate date ranges against periodicity codes
* Maintain consistent periodicity choices
* Document business reasons for periodicity selection
* Test leap year scenarios
## Integration with Other Catalogs
### Related Invoice Elements
* **CFDI Usage Codes**: Global invoices typically use "G01" (merchandise acquisition) or "G03" (general expenses)
* **Payment Methods**: Usually "PUE" (immediate payment) for consolidated invoices
* **Tax Regimes**: Compatible with all business tax regimes
### System Requirements
* Date validation for period boundaries
* Leap year calculation capabilities
* Period overlap detection
* Automated period calculation
## Related Documentation
* [Invoice Globals](/guides/catalogs/invoices_globals) - For general global invoice configuration
* [CFDI Usage Catalog](/guides/catalogs/usages) - For compatible usage codes
* [Payment Methods Catalog](/guides/catalogs/payment_methods) - For payment timing options
* [Tax Regimes Catalog](/guides/catalogs/tax_regimes) - For applicable tax regimes
# Payment Forms Catalog (Formas de Pago)
Source: https://docs.gigstack.io/guides/catalogs/payment_forms
Integration guide for Payment Forms Catalog (Formas de Pago)
## Overview
The Payment Forms catalog defines the standardized codes used to identify different payment methods in the system. These codes follow Mexican tax regulations (SAT) and are essential for proper invoice classification and tax compliance.
## What We See
This catalog contains 21 different payment form codes, ranging from traditional methods like cash and checks to modern electronic payment systems. Each code represents a specific way customers can pay for goods or services, with clear distinctions between different types of electronic payments, credit instruments, and special payment arrangements.
## How to Use It
When creating invoices or processing payments, select the appropriate payment form code that matches the actual payment method used by the customer. This ensures proper tax reporting and compliance with Mexican fiscal regulations.
### Usage Guidelines
* **Always use the exact code**: Payment form codes must match exactly as specified
* **Choose the most specific option**: If multiple codes could apply, use the most specific one
* **Document special cases**: For codes like "99" (Por definir), ensure proper documentation
* **Validate before processing**: Verify the payment form code matches the actual payment method
## Payment Forms Table
| Code | Description (English) | Descripción (Español) | Usage Notes |
| - | - | - | - |
| `01` | Cash | Efectivo | Physical currency payments |
| `02` | Nominative check | Cheque nominativo | Checks made out to a specific payee |
| `03` | Electronic funds transfer | Transferencia electrónica de fondos | Bank-to-bank electronic transfers |
| `04` | Credit card | Tarjeta de crédito | Credit card payments |
| `05` | Electronic wallet | Monedero electrónico | Digital wallet services |
| `06` | Electronic money | Dinero electrónico | Digital currency systems |
| `08` | Food vouchers | Vales de despensa | Employee benefit vouchers |
| `12` | Payment in kind | Dación en pago | Payment with goods instead of money |
| `13` | Subrogation payment | Pago por subrogación | Third-party payment on behalf of debtor |
| `14` | Consignment payment | Pago por consignación | Court-ordered deposit payment |
| `15` | Debt forgiveness | Condonación | Voluntary debt cancellation |
| `17` | Compensation | Compensación | Offsetting mutual debts |
| `23` | Novation | Novación | Replacing old debt with new obligation |
| `24` | Confusion | Confusión | Creditor and debtor become same person |
| `25` | Debt remission | Remisión de deuda | Formal debt release |
| `26` | Statute of limitations | Prescripción o caducidad | Debt expired by time limit |
| `27` | Creditor satisfaction | A satisfacción del acreedor | Payment accepted by creditor |
| `28` | Debit card | Tarjeta de débito | Debit card payments |
| `29` | Service card | Tarjeta de servicios | Prepaid service cards |
| `30` | Advance application | Aplicación de anticipos | Using previously paid advances |
| `31` | Payment intermediary | Intermediario pagos | Third-party payment processors |
| `99` | To be defined | Por definir | Placeholder for undefined methods |
## Implementation Examples
### API Request Example
```json theme={null}
{
"invoice": {
"payment_form": "04",
"amount": 1500.0,
"currency": "MXN"
}
}
```
### Validation Logic
```javascript theme={null}
const validPaymentForms = [
'01',
'02',
'03',
'04',
'05',
'06',
'08',
'12',
'13',
'14',
'15',
'17',
'23',
'24',
'25',
'26',
'27',
'28',
'29',
'30',
'31',
'99',
]
function validatePaymentForm(code) {
return validPaymentForms.includes(code)
}
```
## Common Use Cases
### Electronic Payments
* **Code 03**: Bank transfers, wire transfers
* **Code 04**: Visa, MasterCard, American Express
* **Code 05**: PayPal, digital wallets
* **Code 28**: Debit card transactions
### Traditional Payments
* **Code 01**: Cash transactions
* **Code 02**: Business checks
### Special Arrangements
* **Code 12**: Barter or trade agreements
* **Code 15**: Debt forgiveness agreements
* **Code 30**: Using customer deposits or advances
## Important Notes
1. **Tax Compliance**: Always ensure the selected payment form aligns with actual payment method for proper SAT reporting
2. **Audit Trail**: Maintain documentation linking payment form codes to actual transaction records
3. **Electronic vs Physical**: Distinguish clearly between electronic payment methods (codes 03, 05, 06) and physical cards (codes 04, 28)
4. **Legal Implications**: Codes 12-17 and 23-27 represent legal payment arrangements that may require additional documentation
## Related Documentation
* [Payment Methods Catalog](/guides/catalogs/payment_methods) - For payment method classifications
* [Tax Regimes Catalog](/guides/catalogs/tax_regimes) - For tax-related classifications
* [Invoice Globals](/guides/catalogs/invoices_globals) - For general invoice configuration
# Payment Methods Catalog (Método de Pago)
Source: https://docs.gigstack.io/guides/catalogs/payment_methods
Integration guide for Payment Methods Catalog (Método de Pago)
## Overview
The Payment Methods catalog defines the two fundamental ways invoices can be structured regarding payment timing in the Mexican CFDI system. This classification determines whether an invoice requires immediate payment or allows for deferred/partial payments, which has significant implications for tax reporting and document management.
## What We See
This catalog contains only 2 payment method codes, but these represent fundamentally different business models:
* **PUE**: Immediate payment transactions (cash sales)
* **PPD**: Credit transactions requiring payment tracking and additional documentation
Unlike payment forms (which specify HOW payment is made), payment methods specify WHEN payment occurs relative to the invoice issuance.
## How to Use It
Select the payment method that matches your business transaction model. This decision affects the entire invoice lifecycle, from initial issuance to final payment tracking and tax compliance requirements.
### Usage Guidelines
* **Choose based on payment timing**: Use PUE for immediate payments, PPD for credit arrangements
* **Consider document requirements**: PPD requires additional payment complement documents
* **Plan for compliance**: PPD invoices require ongoing payment tracking until fully paid
* **Validate business model**: Ensure your payment method aligns with actual business practices
## Payment Methods Table
| Code | Description (English) | Descripción (Español) | Payment Timing | Additional Requirements |
| - | - | - | - | - |
| `PUE` | Single payment (cash) | Pago en una sola exhibición (de contado) | Immediate/concurrent with invoice | None - payment is complete |
| `PPD` | Installment or deferred payment | Pago en parcialidades o diferido | Future/partial payments allowed | Payment complement documents required |
## PUE vs PPD: Key Differences
### PUE (Pago en Una Exhibición)
* **Payment Timing**: Payment is made immediately when the invoice is issued
* **Use Cases**: Cash sales, immediate bank transfers, point-of-sale transactions
* **Documentation**: Single invoice document suffices
* **Tax Implications**: Full tax liability established at invoice date
* **Business Impact**: Simplified accounting, immediate cash flow
### PPD (Pago en Parcialidades o Diferido)
* **Payment Timing**: Payment occurs after invoice issuance, may be partial or in installments
* **Use Cases**: Credit sales, layaway plans, subscription services, B2B credit terms
* **Documentation**: Requires payment complement (Complemento de Pago) for each payment received
* **Tax Implications**: Tax liability established at invoice date, but payment tracking required
* **Business Impact**: Extended payment terms, accounts receivable management needed
## Implementation Examples
### API Request Examples
#### PUE Invoice
```json theme={null}
{
"invoice": {
"payment_method": "PUE",
"payment_form": "04",
"total": 1160.0,
"status": "paid"
}
}
```
#### PPD Invoice
```json theme={null}
{
"invoice": {
"payment_method": "PPD",
"payment_form": "99",
"total": 5800.0,
"status": "pending_payment",
"due_date": "2024-02-15"
}
}
```
### Validation Logic
```javascript theme={null}
const validPaymentMethods = ['PUE', 'PPD']
function validatePaymentMethod(method) {
return validPaymentMethods.includes(method)
}
function validatePaymentMethodConfiguration(invoice) {
const { payment_method, payment_form, status } = invoice
if (payment_method === 'PUE') {
// PUE invoices should be paid immediately
if (status !== 'paid') {
throw new Error('PUE invoices must be marked as paid')
}
// Payment form should be specific (not "99")
if (payment_form === '99') {
throw new Error('PUE invoices require specific payment form')
}
}
if (payment_method === 'PPD') {
// PPD invoices can have pending status
if (!['pending_payment', 'partial_payment', 'paid'].includes(status)) {
throw new Error('Invalid status for PPD invoice')
}
// Payment form can be "99" (to be defined)
// Payment complements will define actual payment forms
}
return true
}
```
### Payment Complement for PPD
```javascript theme={null}
// When receiving payment for PPD invoice
function createPaymentComplement(ppdInvoice, paymentDetails) {
return {
type: 'payment_complement',
related_invoice: ppdInvoice.uuid,
payment_date: paymentDetails.date,
payment_amount: paymentDetails.amount,
payment_form: paymentDetails.form, // Actual payment method used
exchange_rate: paymentDetails.exchange_rate || 1,
remaining_balance: ppdInvoice.total - paymentDetails.amount,
}
}
```
## Business Scenarios
### When to Use PUE
* **Retail Sales**: Customer pays at point of sale
* **Restaurant Transactions**: Payment upon service completion
* **Online Purchases**: Payment processed during checkout
* **Professional Services**: Payment upon service delivery
* **Small Transactions**: Where credit terms are unnecessary
### When to Use PPD
* **B2B Sales**: 30, 60, or 90-day payment terms
* **Large Purchases**: Customer needs time to arrange financing
* **Subscription Services**: Monthly/annual billing cycles
* **Construction Projects**: Progress-based payments
* **Layaway Programs**: Customer pays in installments
## Payment Complement Requirements (PPD Only)
When using PPD, you must issue payment complement documents for each payment received:
### Required Information
* Original invoice UUID reference
* Payment date and amount
* Actual payment form used (01, 02, 03, etc.)
* Exchange rate (if applicable)
* Bank account information (if applicable)
### Compliance Timeline
* Payment complements must be issued within specific timeframes
* All payments must be tracked until invoice is fully paid
* Proper accounting entries required for each payment
## Common Implementation Patterns
### E-commerce Platform
```javascript theme={null}
function determinePaymentMethod(order) {
if (order.payment_processor === 'stripe' && order.status === 'completed') {
return 'PUE' // Immediate payment
}
if (order.terms === 'net_30' || order.payment_plan) {
return 'PPD' // Credit terms or payment plan
}
return 'PUE' // Default to immediate payment
}
```
### B2B Invoice System
```javascript theme={null}
function createInvoice(customer, items, terms) {
const invoice = {
customer_id: customer.id,
items: items,
total: calculateTotal(items),
payment_method: terms.credit_days > 0 ? 'PPD' : 'PUE',
payment_form: terms.credit_days > 0 ? '99' : determinePaymentForm(terms),
due_date: terms.credit_days > 0 ? addDays(new Date(), terms.credit_days) : null,
}
return invoice
}
```
## Important Considerations
### Tax Implications
1. **PUE**: Tax obligation and payment occur simultaneously
2. **PPD**: Tax obligation established at invoice date, regardless of payment status
3. **Deductibility**: Expenses may be deductible at different times depending on method
### Cash Flow Management
1. **PUE**: Immediate cash collection, predictable cash flow
2. **PPD**: Extended collection periods, requires working capital management
3. **Risk Assessment**: PPD carries credit risk that must be managed
### System Requirements
1. **PUE**: Standard invoicing system sufficient
2. **PPD**: Requires payment tracking, complement generation, and accounts receivable management
3. **Integration**: Payment processing systems must align with chosen method
## Error Prevention
### Common Mistakes
* Using PUE for credit sales (creates compliance issues)
* Using PPD for immediate transactions (unnecessary complexity)
* Forgetting to issue payment complements for PPD
* Mixing payment methods inappropriately
### Best Practices
* Clearly define payment terms in customer agreements
* Implement automated payment complement generation for PPD
* Train staff on the implications of each payment method
* Regular reconciliation of PPD invoices and payments
## Related Documentation
* [Payment Forms Catalog](/guides/catalogs/payment_forms) - For specific payment method codes
* [CFDI Usage Catalog](/guides/catalogs/usages) - For expense classification
* [Tax Regimes Catalog](/guides/catalogs/tax_regimes) - For tax regime compatibility
* [Invoice Globals](/guides/catalogs/invoices_globals) - For general invoice configuration
# Tax Regimes Catalog (Régimen Fiscal)
Source: https://docs.gigstack.io/guides/catalogs/tax_regimes
Integration guide for Tax Regimes Catalog (Régimen Fiscal)
## Overview
The Tax Regimes catalog defines the legal tax frameworks under which Mexican taxpayers operate. Each regime has specific rights, obligations, and tax calculation methods established by the Mexican Tax Administration Service (SAT). The correct regime selection is crucial for legal compliance and determines which CFDI usage codes can be applied.
## What We See
This catalog contains 23 tax regime codes covering various taxpayer categories:
* **Corporate entities** (601, 603, 620-624, 628, 629)
* **Individual taxpayers** (605, 608, 612, 614, 615, 625, 626)
* **Specialized activities** (606, 607, 622, 628, 630)
* **Special arrangements** (609, 610, 616)
Each regime has specific requirements for income reporting, expense deductions, and tax calculations.
## How to Use It
Tax regime selection is typically determined by legal entity type, income sources, and business activities. This is usually set during taxpayer registration and rarely changes. When creating invoices, ensure the issuer's tax regime is correctly specified and compatible with the selected CFDI usage codes.
### Usage Guidelines
* **Use registered regime**: Always use the regime officially registered with SAT
* **Verify compatibility**: Ensure CFDI usage codes are compatible with the regime
* **Update when changed**: Reflect any official regime changes in the system
* **Validate during onboarding**: Verify customer/supplier tax regimes during setup
## Tax Regimes by Category
### Corporate Entities (Personas Morales)
| Code | Description | Entity Type | Common Use Cases |
| - | - | - | - |
| `601` | General Law for Legal Entities | Corporations | Standard business corporations, LLCs |
| `603` | Non-Profit Legal Entities | Non-profits | Foundations, NGOs, charitable organizations |
| `620` | Production Cooperatives (income deferral option) | Cooperatives | Worker-owned production cooperatives |
| `621` | Fiscal Incorporation | Small businesses | Simplified regime for small corporations |
| `623` | Optional for Corporate Groups | Holding companies | Multi-company corporate structures |
| `624` | Coordinated Entities | Special entities | Government-coordinated entities |
| `628` | Hydrocarbons | Energy sector | Oil, gas, and energy companies |
| `629` | Preferential Tax Regimes and Multinationals | Multinationals | International corporations, tax-preferred entities |
### Individual Taxpayers (Personas Físicas)
| Code | Description | Taxpayer Type | Income Sources |
| - | - | - | - |
| `605` | Salaries and Salary-Similar Income | Employees | Employment income, wages, salaries |
| `608` | Other Income | Individuals | Miscellaneous income not in other categories |
| `612` | Individuals with Business and Professional Activities | Entrepreneurs | Freelancers, consultants, small business owners |
| `614` | Interest Income | Investors | Bank interest, investment returns |
| `615` | Prize Income Regime | Prize winners | Lottery, contest, and prize income |
| `625` | Business Activities through Technological Platforms | Platform workers | Uber, Airbnb, freelance platforms |
| `626` | Simplified Confidence Regime | Small taxpayers | Simplified tax regime for small businesses |
### Specialized Activities
| Code | Description | Activity Type | Specific Requirements |
| - | - | - | - |
| `606` | Rental Income | Property owners | Real estate rental income |
| `607` | Acquisition or Sale of Goods | Traders | Goods trading, asset transactions |
| `622` | Agricultural, Livestock, Forestry and Fishing | Primary sector | Farming, ranching, forestry, fishing |
| `630` | Stock Exchange Share Sales | Investors | Stock market transactions |
### Special Arrangements
| Code | Description | Special Case | Usage Context |
| - | - | - | - |
| `609` | Consolidation | Tax consolidation | Consolidated tax filing for corporate groups |
| `610` | Foreign Residents without Permanent Establishment | Foreign entities | International businesses without Mexican operations |
| `616` | Without Tax Obligations | Exempt entities | Government entities, certain exempt organizations |
## Complete Tax Regimes Table
| Code | Description (English) | Descripción (Español) | Entity Type |
| - | - | - | - |
| `601` | General Law for Legal Entities | General de Ley Personas Morales | Corporation |
| `603` | Non-Profit Legal Entities | Personas Morales con Fines no Lucrativos | Non-profit |
| `605` | Salaries and Salary-Similar Income | Sueldos y Salarios e Ingresos Asimilados a Salarios | Individual |
| `606` | Rental Income | Arrendamiento | Individual/Corporate |
| `607` | Acquisition or Sale of Goods | Régimen de Enajenación o Adquisición de Bienes | Individual |
| `608` | Other Income | Demás ingresos | Individual |
| `609` | Consolidation | Consolidación | Corporate Group |
| `610` | Foreign Residents without Permanent Establishment | Residentes en el Extranjero sin Establecimiento Permanente en México | Foreign Entity |
| `611` | Dividend Income (partners and shareholders) | Ingresos por Dividendos (socios y accionistas) | Individual |
| `612` | Individuals with Business and Professional Activities | Personas Físicas con Actividades Empresariales y Profesionales | Individual |
| `614` | Interest Income | Ingresos por intereses | Individual |
| `615` | Prize Income Regime | Régimen de los ingresos por obtención de premios | Individual |
| `616` | Without Tax Obligations | Sin obligaciones fiscales | Exempt Entity |
| `620` | Production Cooperatives (income deferral option) | Sociedades Cooperativas de Producción que optan por diferir sus ingresos | Cooperative |
| `621` | Fiscal Incorporation | Incorporación Fiscal | Small Corporation |
| `622` | Agricultural, Livestock, Forestry and Fishing | Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras | Primary Sector |
| `623` | Optional for Corporate Groups | Opcional para Grupos de Sociedades | Corporate Group |
| `624` | Coordinated Entities | Coordinados | Special Entity |
| `625` | Business Activities through Technological Platforms | Régimen de las Actividades Empresariales con ingresos a través de Plataformas Tecnológicas | Platform Worker |
| `626` | Simplified Confidence Regime | Régimen Simplificado de Confianza | Small Taxpayer |
| `628` | Hydrocarbons | Hidrocarburos | Energy Sector |
| `629` | Preferential Tax Regimes and Multinationals | De los Regímenes Fiscales Preferentes y de las Empresas Multinacionales | Multinational |
| `630` | Stock Exchange Share Sales | Enajenación de acciones en bolsa de valores | Investor |
## Tax Regime and CFDI Usage Compatibility
### Business-Oriented Regimes
**Compatible with G, I, S, CP codes:**
* 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626
### Individual-Oriented Regimes
**Compatible with D, S, CP codes:**
* 605, 606, 608, 611, 612, 614, 607, 615, 625
### Payroll-Specific Regime
**Compatible with CN codes:**
* 605 (exclusively for payroll documents)
### Universal Compatibility
**Compatible with S, CP codes (all regimes):**
* S01 (Without fiscal effects)
* CP01 (Payments)
## Implementation Examples
### API Request Example
```json theme={null}
{
"taxpayer": {
"tax_id": "RFC123456789",
"tax_regime": "601",
"entity_type": "corporation",
"business_name": "Example Corp S.A. de C.V."
}
}
```
### Validation Logic
```javascript theme={null}
const validTaxRegimes = [
'601',
'603',
'605',
'606',
'607',
'608',
'609',
'610',
'611',
'612',
'614',
'615',
'616',
'620',
'621',
'622',
'623',
'624',
'625',
'626',
'628',
'629',
'630',
]
const corporateRegimes = ['601', '603', '620', '621', '623', '624', '628', '629']
const individualRegimes = ['605', '608', '612', '614', '615', '625', '626']
const specializedRegimes = ['606', '607', '622', '630']
const specialArrangements = ['609', '610', '611', '616']
function validateTaxRegime(regime) {
return validTaxRegimes.includes(regime)
}
function getRegimeCategory(regime) {
if (corporateRegimes.includes(regime)) return 'corporate'
if (individualRegimes.includes(regime)) return 'individual'
if (specializedRegimes.includes(regime)) return 'specialized'
if (specialArrangements.includes(regime)) return 'special'
return 'unknown'
}
function validateUsageForRegime(usageCode, taxRegime) {
const usageRegimeCompatibility = {
G01: ['601', '603', '606', '612', '620', '621', '622', '623', '624', '625', '626'],
D01: ['605', '606', '608', '611', '612', '614', '607', '615', '625'],
CN01: ['605'],
S01: validTaxRegimes, // All regimes
CP01: validTaxRegimes, // All regimes
}
const compatibleRegimes = usageRegimeCompatibility[usageCode]
return compatibleRegimes && compatibleRegimes.includes(taxRegime)
}
```
### Entity Type Detection
```javascript theme={null}
function determineEntityType(taxRegime) {
const entityMap = {
601: 'corporation',
603: 'non_profit',
605: 'employee',
606: 'property_owner',
607: 'trader',
608: 'individual_other',
612: 'individual_business',
614: 'investor',
615: 'prize_winner',
620: 'cooperative',
621: 'small_corporation',
622: 'agricultural',
625: 'platform_worker',
626: 'small_taxpayer',
628: 'energy_company',
629: 'multinational',
630: 'stock_trader',
}
return entityMap[taxRegime] || 'unknown'
}
```
## Common Business Scenarios
### Corporate Registration
```javascript theme={null}
// Standard corporation setup
const corporation = {
tax_regime: '601',
entity_type: 'corporation',
applicable_usage_codes: [
'G01',
'G02',
'G03',
'I01',
'I02',
'I03',
'I04',
'I05',
'I06',
'I07',
'I08',
'S01',
'CP01',
],
}
// Small business choosing simplified regime
const smallBusiness = {
tax_regime: '626',
entity_type: 'small_taxpayer',
benefits: ['simplified_filing', 'reduced_obligations'],
applicable_usage_codes: ['G01', 'G02', 'G03', 'S01', 'CP01'],
}
```
### Individual Taxpayer Scenarios
```javascript theme={null}
// Employee receiving salary
const employee = {
tax_regime: '605',
income_type: 'salary',
applicable_usage_codes: [
'D01',
'D02',
'D03',
'D04',
'D05',
'D06',
'D07',
'D08',
'D09',
'D10',
'S01',
'CP01',
'CN01',
],
}
// Freelancer/consultant
const freelancer = {
tax_regime: '612',
activity_type: 'professional_services',
applicable_usage_codes: [
'G01',
'G02',
'G03',
'D01',
'D02',
'D03',
'D04',
'D05',
'D06',
'D07',
'D08',
'D09',
'D10',
'S01',
'CP01',
],
}
// Platform worker (Uber, Airbnb, etc.)
const platformWorker = {
tax_regime: '625',
platform_type: 'ride_sharing',
applicable_usage_codes: ['G01', 'G02', 'G03', 'S01', 'CP01'],
}
```
## Important Considerations
### Legal Requirements
1. **Official Registration**: Tax regime must match SAT registration
2. **Change Procedures**: Regime changes require formal SAT approval
3. **Compliance Obligations**: Each regime has specific filing and payment requirements
4. **Documentation**: Maintain records supporting regime classification
### Business Impact
1. **Tax Rates**: Different regimes have different tax calculation methods
2. **Deduction Limits**: Available deductions vary by regime
3. **Filing Requirements**: Some regimes have simplified filing procedures
4. **Growth Considerations**: May need to change regimes as business grows
### System Integration
1. **Customer Onboarding**: Verify and validate tax regime during registration
2. **Invoice Validation**: Ensure usage codes are compatible with regimes
3. **Reporting**: Generate regime-specific reports for compliance
4. **Updates**: Monitor regime changes and update system accordingly
## Recent Changes and Trends
### Regime 626 (Simplified Confidence Regime)
* Introduced as a replacement for previous simplified regimes
* Designed for small taxpayers with specific income limits
* Simplified tax calculation and filing requirements
### Regime 625 (Platform Workers)
* Created to address the gig economy growth
* Specific rules for platform-mediated income
* Simplified withholding and reporting procedures
## Error Prevention
### Common Mistakes
* Using corporate usage codes with individual regimes
* Incorrect regime selection during customer setup
* Failing to update regime when taxpayer status changes
* Mixing incompatible usage codes and tax regimes
### Best Practices
* Implement regime validation during data entry
* Provide clear regime descriptions to users
* Regular audit of regime assignments
* Stay updated on tax law changes affecting regimes
## Related Documentation
* [CFDI Usage Catalog](/guides/catalogs/usages) - For compatible usage codes by regime
* [Payment Forms Catalog](/guides/catalogs/payment_forms) - For payment method codes
* [Payment Methods Catalog](/guides/catalogs/payment_methods) - For payment timing options
* [Invoice Globals](/guides/catalogs/invoices_globals) - For general invoice configuration
# CFDI Usage Catalog (Uso CFDI)
Source: https://docs.gigstack.io/guides/catalogs/usages
Integration guide for CFDI Usage Catalog (Uso CFDI)
## Overview
The CFDI Usage catalog defines the standardized codes that specify the intended use of expenses for tax deduction purposes in Mexico. These codes are essential for proper tax compliance and determine which tax regimes can utilize specific types of expenses for deductions.
## What We See
This catalog contains 23 different usage codes organized into six main categories:
* **G codes**: General business expenses and merchandise acquisition
* **I codes**: Investment and infrastructure expenses
* **D codes**: Personal deductible expenses (medical, education, etc.)
* **S codes**: Special cases without fiscal effects
* **CP codes**: Payment-related documents
* **CN codes**: Payroll-related documents
Each usage code is associated with specific tax regimes that can legally claim those expenses as deductions.
## How to Use It
When issuing or receiving a CFDI, select the usage code that best describes the intended use of the expense. This ensures proper tax treatment and compliance with SAT regulations. The receiving party's tax regime must be compatible with the selected usage code.
### Usage Guidelines
* **Match expense purpose**: Select the code that most accurately describes the expense's business purpose
* **Verify tax regime compatibility**: Ensure the recipient's tax regime allows the selected usage code
* **Document justification**: Maintain records supporting the usage classification
* **Review regularly**: Usage codes and tax regime compatibility may change with tax law updates
## CFDI Usage Codes by Category
### General Business Expenses (G Codes)
| Code | Description | Compatible Tax Regimes |
| - | - | - |
| `G01` | Merchandise acquisition | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `G02` | Returns, discounts or bonuses | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `G03` | General expenses | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
### Investment Expenses (I Codes)
| Code | Description | Compatible Tax Regimes |
| - | - | - |
| `I01` | Construction | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `I02` | Office furniture and equipment investments | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `I03` | Transportation equipment | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `I04` | Computer equipment and accessories | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `I05` | Dies, molds, matrices and tooling | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `I06` | Telephone communications | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `I07` | Satellite communications | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
| `I08` | Other machinery and equipment | 601, 603, 606, 612, 620, 621, 622, 623, 624, 625, 626 |
### Personal Deductible Expenses (D Codes)
| Code | Description | Compatible Tax Regimes |
| - | - | - |
| `D01` | Medical, dental fees and hospital expenses | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D02` | Medical expenses for disability or incapacity | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D03` | Funeral expenses | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D04` | Donations | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D05` | Real interest effectively paid for mortgage loans (housing) | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D06` | Voluntary contributions to SAR | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D07` | Medical expense insurance premiums | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D08` | Mandatory school transportation expenses | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D09` | Savings account deposits, pension plan premiums | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
| `D10` | Educational service payments (tuition) | 605, 606, 608, 611, 612, 614, 607, 615, 625 |
### Special Cases (S Codes)
| Code | Description | Compatible Tax Regimes |
| - | - | - |
| `S01` | Without fiscal effects | 601, 603, 605, 606, 608, 610, 611, 612, 614, 616, 620, 621, 622, 623, 624, 607, 615, 625, 626 |
### Payment Documents (CP Codes)
| Code | Description | Compatible Tax Regimes |
| - | - | - |
| `CP01` | Payments | 601, 603, 605, 606, 608, 610, 611, 612, 614, 616, 620, 621, 622, 623, 624, 607, 615, 625, 626 |
### Payroll Documents (CN Codes)
| Code | Description | Compatible Tax Regimes |
| - | - | - |
| `CN01` | Payroll | 605 |
## Tax Regime Compatibility Matrix
### Business-Oriented Regimes
Tax regimes **601, 603, 606, 612, 620-626** can use:
* All G codes (general business expenses)
* All I codes (investment expenses)
* S01 (without fiscal effects)
* CP01 (payments)
### Individual-Oriented Regimes
Tax regimes **605, 606, 608, 611, 612, 614, 607, 615, 625** can use:
* All D codes (personal deductible expenses)
* S01 (without fiscal effects)
* CP01 (payments)
### Payroll-Specific Regime
Tax regime **605** exclusively uses:
* CN01 (payroll documents)
## Implementation Examples
### API Request Example
```json theme={null}
{
"invoice": {
"cfdi_usage": "G01",
"tax_regime": "601",
"description": "Office supplies purchase"
}
}
```
### Validation Logic
```javascript theme={null}
const usageTaxRegimeMap = {
G01: ['601', '603', '606', '612', '620', '621', '622', '623', '624', '625', '626'],
G02: ['601', '603', '606', '612', '620', '621', '622', '623', '624', '625', '626'],
G03: ['601', '603', '606', '612', '620', '621', '622', '623', '624', '625', '626'],
D01: ['605', '606', '608', '611', '612', '614', '607', '615', '625'],
CN01: ['605'],
// ... complete mapping
}
function validateUsageCodeForTaxRegime(usageCode, taxRegime) {
const compatibleRegimes = usageTaxRegimeMap[usageCode]
return compatibleRegimes && compatibleRegimes.includes(taxRegime)
}
```
### Usage Code Validation
```javascript theme={null}
const validUsageCodes = [
'G01',
'G02',
'G03',
'I01',
'I02',
'I03',
'I04',
'I05',
'I06',
'I07',
'I08',
'D01',
'D02',
'D03',
'D04',
'D05',
'D06',
'D07',
'D08',
'D09',
'D10',
'S01',
'CP01',
'CN01',
]
function isValidUsageCode(code) {
return validUsageCodes.includes(code)
}
```
## Common Use Cases
### Business Expenses
* **G01**: Purchasing inventory, raw materials, merchandise for resale
* **G02**: Processing customer returns, applying bulk discounts
* **G03**: Office supplies, utilities, general operational expenses
### Capital Investments
* **I01**: Building construction, facility improvements
* **I02**: Desks, chairs, office equipment purchases
* **I03**: Company vehicles, delivery trucks
* **I04**: Computers, servers, software licenses
### Personal Deductions (Individuals)
* **D01**: Doctor visits, dental work, hospital bills
* **D10**: School tuition, university fees
* **D04**: Charitable donations to qualified organizations
### Special Situations
* **S01**: Transactions without tax implications
* **CP01**: Payment complement documents
* **CN01**: Employee salary payments
## Important Notes
1. **Tax Regime Validation**: Always verify that the recipient's tax regime is compatible with the selected usage code
2. **Business vs Personal**: D codes are primarily for individual taxpayers, while G and I codes are for business entities
3. **Documentation Requirements**: Maintain supporting documentation that justifies the usage code selection
4. **Regular Updates**: Usage codes and tax regime compatibility may change with tax law modifications
5. **Audit Compliance**: Proper usage code selection is crucial for tax audits and compliance reviews
## Error Prevention
### Common Mistakes
* Using D codes for business entities (incorrect tax regime)
* Selecting G01 for capital investments (should use I codes)
* Using CN01 for non-payroll tax regimes
* Mismatching usage codes with actual expense purposes
### Best Practices
* Maintain a clear mapping of business activities to usage codes
* Train accounting staff on proper code selection
* Implement validation rules in invoicing systems
* Review usage patterns regularly for compliance
## Related Documentation
* [Tax Regimes Catalog](/guides/catalogs/tax_regimes) - For detailed tax regime information
* [Payment Forms Catalog](/guides/catalogs/payment_forms) - For payment method classifications
* [Invoice Globals](/guides/catalogs/invoices_globals) - For general invoice configuration
# Clients API Guide
Source: https://docs.gigstack.io/guides/clients
Integration guide for Clients
Some operations in this guide have Discovery handlers but no public gateway route yet: `POST /clients/{id}/support-documents`. Check the availability notice on each API reference page before using them.
Manage clients with fiscal information for Mexican tax compliance. The Clients API handles customer data, RFC validation, EFOS checking, and SAT compliance requirements.
## Overview
Clients are the foundation of your invoicing system. Each client contains fiscal information required for Mexican tax compliance, including RFC (tax ID), tax system, and address information.
## Key Features
* **RFC Validation** - Automatic tax ID validation against SAT
* **EFOS Checking** - Blacklist validation for compliance
* **Address Management** - Mexican address structure support
* **Metadata Support** - Custom fields for additional data
* **Duplicate Prevention** - Search for existing clients before creating (upsert-like behavior)
* **Auto-creation** - Create clients during invoice/payment flow
## Endpoints
### List Clients
```http theme={null}
GET /clients
```
Retrieve a paginated list of clients with filtering and search capabilities.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `next` | string | Pagination cursor for next page |
| `team` | string | gigstack Connect: Target team ID |
| `order_by` | string | Field to sort by (e.g., `created_at`) |
| `sort` | string | Sort direction (`asc`, `desc`) |
| `created_gte` | integer | Filter by creation date (greater than or equal to timestamp) |
| `created_lte` | integer | Filter by creation date (less than or equal to timestamp) |
| `tax_id` | string | Find a client by tax ID / RFC (e.g., `tax_id=PEGJ800101ABC`) |
| `metadata.{key}` | string | Filter by metadata field using dot notation (e.g., `metadata.external_id=EXT-123`) |
| `metadata_{key}` | string | Filter by metadata field using underscore notation (e.g., `metadata_external_id=EXT-123`) |
| `page` | integer | Page number for pagination when using metadata filters (default: 1) |
**Metadata Filtering:**
You can filter clients by any metadata key using either dot notation or underscore notation. Both formats are equivalent and supported:
```bash theme={null}
# Dot notation
GET /clients?metadata.external_id=EXT-123
# Underscore notation (alternative)
GET /clients?metadata_external_id=EXT-123
```
The metadata filtering uses Typesense search for efficient querying without requiring Firestore indexes. When using metadata filters, pagination is controlled via the `page` parameter instead of the `next` cursor.
**Example Requests:**
```bash theme={null}
# Basic listing
curl -X GET "https://api.gigstack.io/v2/clients?limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by creation date
curl -X GET "https://api.gigstack.io/v2/clients?created_gte=1700000000000&created_lte=1710000000000" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by metadata (dot notation)
curl -X GET "https://api.gigstack.io/v2/clients?metadata.external_id=EXT-123" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by metadata (underscore notation)
curl -X GET "https://api.gigstack.io/v2/clients?metadata_customer_tier=premium" \
-H "Authorization: Bearer YOUR_TOKEN"
# Multiple metadata filters with pagination
curl -X GET "https://api.gigstack.io/v2/clients?metadata.region=north&metadata.status=active&page=2&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Clients retrieved successfully",
"data": [
{
"id": "client_1234567890",
"name": "Juan Pérez García",
"company": "Empresa SA de CV",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601",
"livemode": true,
"created_at": 1677651234,
"efos": {
"is_valid": true
}
}
],
"has_more": false,
"total_results": 1
}
```
### Create Client
```http theme={null}
POST /clients
```
Create a new client with fiscal information.
**Duplicate Prevention (Upsert):** Use the `search` parameter to find existing clients before creating:
* If a match is found and `search.update` is `false` (default): Returns the existing client without modifications (200).
* If a match is found and `search.update` is `true`: Updates the existing client with the provided data and returns it (200).
* If no match is found: Creates a new client (201).
* If multiple matches are found: Returns a 409 Conflict error with the list of matching client IDs.
**Request Body:**
```json theme={null}
{
"name": "Juan Pérez García",
"company": "Empresa SA de CV",
"email": "juan.perez@ejemplo.com",
"phone": "+52 55 1234 5678",
"tax_id": "PEGJ800101ABC",
"tax_system": "601",
"use": "P01",
"legal_name": "Juan Pérez García",
"address": {
"country": "MEX",
"street": "Av. Insurgentes Sur",
"exterior": "123",
"interior": "4B",
"neighborhood": "Del Valle",
"municipality": "Benito Juárez",
"city": "Ciudad de México",
"state": "CDMX",
"zip": "03100"
},
"metadata": {
"custom_field": "value"
},
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": false
}
}
```
**Search Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `on_key` | string | The field to search on (e.g., `tax_id`, `email`, `name`) |
| `on_value` | string | The value to match against the specified field |
| `update` | boolean | If `true` and a match is found, update the existing client with the provided data. Default: `false` |
**Example Request (Simple):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/clients \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601",
"use": "P01"
}'
```
**Example Request (With Duplicate Prevention):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/clients \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601",
"use": "P01",
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": false
}
}'
```
**Response Codes:**
| Code | Description |
| - | - |
| `200` | Existing client found (when using `search`) |
| `201` | Client created successfully |
| `409` | Multiple clients match the search criteria |
### Get Client
```http theme={null}
GET /clients/{id}
```
Retrieve a specific client by ID.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/clients/client_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Update Client
```http theme={null}
PUT /clients/{id}
```
Update an existing client's information.
**Body Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `check_pending_receipts` | boolean | When `true` (default), after the update succeeds and the client has valid fiscal information, automatically invoice all of the client's pending receipts using the new client data. Set to `false` to skip this behavior. |
The response includes a `pending_receipts` summary (`attempted`, `succeeded`, `failed`, `skipped`, `failures`) when the check is performed.
**Example Request:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/clients/client_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"phone": "+52 55 9876 5432",
"address": {
"zip": "03200"
}
}'
```
**Skip the pending-receipts invoicing:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/clients/client_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"phone": "+52 55 9876 5432",
"check_pending_receipts": false
}'
```
### Delete Client
```http theme={null}
DELETE /clients/{id}
```
Delete a specific client.
**Example Request:**
```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/clients/client_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Validate Client
```http theme={null}
POST /clients/validate/{id}
```
Validate client's fiscal information against SAT.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/clients/validate/client_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Stamp Pending Receipts
```http theme={null}
POST /clients/{id}/stamp-pending-receipts
```
Stamps every pending receipt that belongs to this client, **to the client's own fiscal identity**.
> **This issues real CFDIs.** Each stamped receipt becomes a live invoice with the SAT, using the client's `rfc`, `legal_name`, `address.zip` and `tax_system`. There is no dry-run mode. Cancel through `DELETE /invoices/{id}` if you stamp by mistake.
**Preconditions** — the client document must already carry the fiscal data required by the CFDI:
| Field | Notes |
| - | - |
| `rfc` | Falls back to `tax_id` if `rfc` is absent |
| `legal_name` | Falls back to `name` if `legal_name` is absent |
| `address.zip` | Required |
| `tax_system` | Required (SAT régimen fiscal code) |
If any of these is missing the call returns `400` with code `client_fiscal_data_incomplete` and a `details` string naming the missing field(s). Nothing is stamped in that case.
**Scope** — only receipts that match *all* of the following are considered:
* `team` equals the effective team of the API key (or the `?team=` Connect target)
* `livemode` equals the mode of the API key (a test key never touches live receipts)
* `status` is `pending`
* `client.id` equals `{id}`
**Batching** — a maximum of **100 receipts are stamped per call**. The response's `remaining` field reports how many pending receipts are still left for this client (including any that failed in this batch, since a failed receipt stays `pending`). Keep calling the endpoint until `remaining` is `0` to drain a backlog.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/clients/client_1234567890/stamp-pending-receipts \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"message": "Stamped 98 receipt(s), 2 failed",
"data": {
"stamped": 98,
"failed": 2,
"remaining": 27,
"results": [
{ "id": "receipt_1234567890", "status": "stamped" },
{ "id": "receipt_0987654321", "status": "failed", "error": "..." }
]
},
"timestamp": 1730000000000
}
```
When there is nothing to do the endpoint returns `200` with `{ "stamped": 0, "failed": 0, "remaining": 0, "results": [] }`.
**Drain loop:**
```bash theme={null}
while true; do
remaining=$(curl -s -X POST \
https://api.gigstack.io/v2/clients/client_1234567890/stamp-pending-receipts \
-H "Authorization: Bearer YOUR_TOKEN" | jq '.data.remaining')
[ "$remaining" -eq 0 ] && break
done
```
### Customer Portal Access
```http theme={null}
POST /clients/customerportal
```
Creates a single-use customer-portal session for a client and returns the URL to send them.
The client is identified **in the request body**, not in the path — pass either `id` or `email` (if both are present, `id` wins). Omitting both returns `400 "Client ID or email is required"`.
The team must have a customer portal configured (`customerPortalId`); otherwise the call returns `400 "Team customer portal is not configured"`.
**Request Body:**
| Field | Type | Required | Description |
| - | - | - | - |
| `id` | string | one of `id`/`email` | Client ID |
| `email` | string | one of `id`/`email` | Client email, used when `id` is not supplied |
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/clients/customerportal \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "id": "client_1234567890" }'
```
```bash theme={null}
# By email instead
curl -X POST https://api.gigstack.io/v2/clients/customerportal \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email": "juan.perez@ejemplo.com" }'
```
**Example Response:**
```json theme={null}
{
"success": true,
"message": "Customer portal retrieved successfully",
"data": {
"url": "https://portal.gigstack.pro/{customerPortalId}?sessionId=...&c=...",
"expires_at": 1730432000000,
"session_id": "otpcustomerportal..."
},
"timestamp": 1730000000000
}
```
The link is valid for **5 days** (`expires_at`, epoch ms). Treat the URL as a credential — anyone holding it can see that client's documents.
### Upload CSF (Constancia de Situación Fiscal)
```http theme={null}
POST /clients/csf
```
Upload the SAT's CSF PDF to create a client from it, or to update an existing client. gigstack reads the RFC and CIF from the PDF, validates them against the SAT, and fills in the legal name, RFC, tax regime, fiscal type and status, and the full fiscal address.
Send the PDF as `multipart/form-data` in a `file` field. Add `?client_id=` to update that client; omit it to create a new one.
```bash theme={null}
# Create a client from a CSF
curl -X POST https://api.gigstack.io/v2/clients/csf \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@constancia.pdf"
# Update an existing client
curl -X POST "https://api.gigstack.io/v2/clients/csf?client_id=client_1234567890" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@constancia.pdf"
```
Returns `201` (`message: "Client created from CSF"`) or `200` (`message: "Client updated with CSF data"`) with the client in `data`, in the standardized envelope. An unreadable PDF, missing fiscal data or an unknown `client_id` returns `400`.
### Support Documents
```http theme={null}
POST /clients/{id}/support-documents
GET /clients/{id}/support-documents
```
Attach and list supporting documents (contracts, communications…) for a client. Same fields, limits and response as [invoice support documents](/guides/invoices#support-documents): `multipart/form-data` with `file` (PDF, PNG, JPG or WEBP, up to 10 MB) and `documentType`; answers `201`.
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/clients/client_1234567890/support-documents \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@contrato.pdf" \
-F "documentType=contract"
```
## Client Structure
### Required Fields
* **None** - All fields are optional for maximum flexibility
### Important Fields
* `tax_id` (string) - RFC for Mexican tax compliance
* `tax_system` (string) - SAT tax system code (e.g., "601", "612")
* `use` (string) - Default CFDI use code (e.g., "P01", "G03")
* `email` (string) - Email for invoice delivery
### Tax System Codes
Common SAT tax system codes:
* `601` - General de Ley Personas Morales
* `612` - Persona Física con Actividades Empresariales
* `621` - Incorporación Fiscal
* `622` - Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras
* `623` - Opcional para Grupos de Sociedades
* `624` - Coordinados
* `625` - Régimen de las Actividades Empresariales con ingresos a través de Plataformas Tecnológicas
* `626` - Régimen Simplificado de Confianza
### CFDI Use Codes
Common CFDI use codes:
* `P01` - Por definir
* `G01` - Adquisición de mercancías
* `G02` - Devoluciones, descuentos o bonificaciones
* `G03` - Gastos en general
* `I01` - Construcciones
* `I02` - Mobiliario y equipo de oficina por inversiones
* `I03` - Equipo de transporte
* `I04` - Equipo de computo y accesorios
* `I05` - Dados, troqueles, moldes, matrices y herramental
* `I06` - Comunicaciones telefónicas
* `I07` - Comunicaciones satelitales
* `I08` - Otra maquinaria y equipo
## Search and Duplicate Prevention
The `search` parameter enables upsert-like behavior when creating clients. This is useful for integrations that may send the same client multiple times.
### Direct Client Creation (POST /clients)
```json theme={null}
{
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601",
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": false
}
}
```
### Within Invoices or Payments
When creating invoices or payments, you can search for existing clients inline:
```json theme={null}
{
"client": {
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": true
},
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601"
}
}
```
### Search Behavior
| Scenario | Result |
| - | - |
| Single match found, `update: false` | Returns existing client unchanged |
| Single match found, `update: true` | Updates client with provided data, returns updated client |
| No match found | Creates new client automatically |
| Multiple matches found | Returns 409 Conflict with list of matching client IDs |
### Example: Update Existing Client on Match
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/clients \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Juan Pérez García",
"email": "nuevo.email@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601",
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": true
}
}'
```
This will find the client with `tax_id: PEGJ800101ABC` and update their email to `nuevo.email@ejemplo.com`.
## Validation and Compliance
### RFC Validation
* Automatic format validation
* SAT registry verification
### EFOS Checking
* Blacklist validation against SAT's EFOS list
* Automatic status updates
* Compliance reporting
### Address Validation
* Mexican postal code verification
* Address completeness checking
## Best Practices
1. **Always include tax\_id** for Mexican clients
2. **Set appropriate tax\_system** based on client type
3. **Use metadata** for custom business logic
4. **Validate clients** before important transactions
5. **Keep addresses updated** for compliance
6. **Use search functionality** to avoid duplicates
## Related Resources
* [Invoices API](/guides/invoices) - Create invoices for clients
* [Payments API](/guides/payments) - Process payments from clients
* [Teams API](/guides/teams) - Manage team settings that affect clients
## Error Handling
Common error scenarios:
### Invalid RFC Format
```json theme={null}
{
"message": "Invalid tax_id format",
"error": "RFC must be 12 or 13 characters"
}
```
### Client Not Found
```json theme={null}
{
"message": "Client not found",
"error": "The specified client does not exist"
}
```
### EFOS Validation Failed
```json theme={null}
{
"message": "Client failed EFOS validation",
"error": "Client is on SAT blacklist"
}
```
### Multiple Clients Match Search Criteria (409 Conflict)
When using the `search` parameter and multiple clients match the criteria:
```json theme={null}
{
"success": false,
"error": {
"code": "resource_conflict",
"message": "Multiple clients found matching tax_id=\"PEGJ800101ABC\". Please use a more specific search criteria.",
"details": ["client_1234567890", "client_0987654321"]
}
}
```
**Resolution:** Use one of the returned client IDs directly, or use a more specific search key (e.g., combine with `email` or use the client `id` directly).
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Descarga Masiva SAT Guide
Source: https://docs.gigstack.io/guides/descarga-masiva
Integration guide for Descarga Masiva SAT Guide
Download all your issued and received CFDI invoices directly from SAT using your FIEL (Firma Electrónica Avanzada). Descarga Masiva gives you a complete history of every invoice associated with your RFC — whether you issued it in gigstack or not.
## Overview
The Descarga Masiva API lets you connect your RFC to SAT's bulk download service and retrieve your full invoice history. You can run one-time requests for specific date ranges, or set up a daily schedule that automatically syncs new invoices.
## Key Features
* **Full Invoice History** - Download all issued and received CFDIs from SAT, regardless of origin
* **Automatic Daily Sync** - Schedule recurring downloads to keep your data up to date
* **FIEL Authentication** - Secure connection using your electronic signature (FIEL)
* **Live Status Updates** - Status polling refreshes pending requests in real time
* **Metered Billing** - Pay only for what you download (\$0.20 MXN per XML)
## Prerequisites
Before using Descarga Masiva:
1. Your team must have a valid RFC configured — and it must be **the same RFC the FIEL belongs to**. See [Step 3](#step-3-—-upload-your-fiel); this is the single most common way onboarding fails.
2. You need an active paid gigstack plan
3. You must have your FIEL files (`.cer` + `.key`) and their password ready
> **FIEL vs CSD:** The FIEL (Firma Electrónica Avanzada) is your personal digital identity certificate used to authenticate with SAT. It is different from the CSD (Certificado de Sello Digital), which is used to stamp CFDI invoices. You need both for full gigstack functionality.
***
## Setup Flow
### Step 1 — Check Activation Status
Before anything else, check whether Descarga Masiva is activated for your team.
```http theme={null}
GET /v2/invoices/download/activate/status
```
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/download/activate/status" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Response:**
```json theme={null}
{
"success": true,
"data": {
"status": "needs_activation",
"planIncludesFeature": true,
"isActivated": false,
"pricing": {
"perDownload": "$0.20 MXN",
"addonMonthly": null
}
}
}
```
**Status values:**
| Status | Meaning | Next step |
| - | - | - |
| `active` | Ready to use | Go to Step 3 |
| `needs_activation` | Your plan includes it, not yet turned on | Call `POST /v2/invoices/download/activate` |
| `needs_addon` | Paid plan, feature not included | Call `POST /v2/invoices/download/activate` — adds the per-download meter only |
| `needs_upgrade` | Free plan | Upgrade your plan first |
***
### Step 2 — Activate
```http theme={null}
POST /v2/invoices/download/activate
```
```bash theme={null}
curl -X POST "https://api.gigstack.io/v2/invoices/download/activate" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Response:**
```json theme={null}
{
"success": true,
"message": "Descarga Masiva activada. Cada descarga de XML consumirá créditos SAT.",
"data": {
"type": "included",
"activated": true
}
}
```
**Billing:**
* If your plan includes Descarga Masiva (`type: "included"`): activation is free — you only pay \$0.20 MXN per XML downloaded.
* If activating as an add-on (`type: "addon"`): also $0.20 MXN per XML downloaded. There is no monthly base fee — the former $400 MXN/RFC/month charge was removed, and `pricing.addonMonthly` now returns `null`.
***
### Step 3 — Upload your FIEL
This is the main setup step. Upload your `.cer` and `.key` files along with the password. gigstack validates your FIEL, securely stores the credentials, and automatically registers your RFC with SAT — all in one request.
> ### ⚠️ Read this before you upload: the FIEL's RFC must equal the team's RFC
>
> The upload is rejected with **`400 "RFC mismatch"`** unless the RFC inside the `.cer` is **exactly** the `rfc` stored on the team being addressed by the request. The error names both values:
>
> ```json theme={null}
> {
> "success": false,
> "message": "RFC mismatch",
> "error": "FIEL certificate RFC (…) does not match your team RFC (…). Each RFC requires its own team."
> }
> ```
>
> Three consequences worth internalizing:
>
> 1. **The team that matters is the one in `?team=`, not the master team.** Under gigstack Connect the request resolves against the *connected* team named by the query parameter. Uploading a connected client's FIEL while pointed at your master team fails, and uploading with `?team=` set to a connected team whose RFC differs from the certificate fails too.
> 2. **Each RFC requires its own team.** There is no way to attach a second RFC to an existing team. One RFC ⇄ one team, always.
> 3. **A company FIEL cannot go on a team created with an individual's RFC.** A *persona moral* RFC is 12 characters; a *persona física* RFC is 13. If the team was created with the legal representative's personal RFC and the FIEL is the company's, every upload attempt will fail — and there is no way to fix it from this endpoint.
>
> **Practical rule: create the team with the RFC that the FIEL will belong to.** For a business that is the business RFC, not the RFC of the director, the accountant, or whoever happens to hold the e-firma. This is decided at team creation (`POST /v2/teams`, or the `rfc` field of `POST /v2/auth/signup`) — long before anyone touches a certificate — which is exactly why it goes wrong: the mistake surfaces only here, at the very end of onboarding, after credentials have already been collected from the client. Verify the RFC on the team *before* asking anyone for their FIEL.
```http theme={null}
POST /v2/invoices/download/fiel
Content-Type: multipart/form-data
```
```bash theme={null}
curl -X POST "https://api.gigstack.io/v2/invoices/download/fiel?team=team_connected123" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "cert=@/path/to/your.cer" \
-F "key=@/path/to/your.key" \
-F "password=YOUR_FIEL_PASSWORD" \
-F "phone=+5215512345678"
```
**Parameters:**
| Field | Type | Required | Description |
| - | - | - | - |
| `cert` | file | Yes | `.cer` file from SAT. Must be DER-encoded, unexpired, and its RFC must equal the team's `rfc`. |
| `key` | file | Yes | `.key` file from SAT (DER-encoded) |
| `password` | string | Yes | Password for the `.key` file |
| `phone` | string | Recommended | Contact phone in international format (`+52...`) |
**Query parameters:**
| Field | Required | Description |
| - | - | - |
| `team` | Under Connect | The team the FIEL is being attached to. The RFC check runs against **this** team. Omit it and the credentials go to the API key's own team. |
> `sync_start_date` is **not** accepted. The historical sync window is always set to the maximum SAT allows (71 months back); sending the field has no effect.
**Other `400` responses from this endpoint**, all with `success: false`:
| `message` | What to do |
| - | - |
| `Team RFC not configured` | The team has no `rfc`. Set it before uploading — and set it to the RFC of the FIEL. |
| `Missing certificate file` / `Missing private key file` / `Missing password` | Supply all three multipart parts. |
| `Invalid certificate file format` / `Invalid private key file format` | The file is not DER-encoded. Upload the originals from SAT, not a converted or re-exported copy. |
| `Could not extract RFC from certificate` | The certificate parsed but carries no RFC — usually not a FIEL. |
| `RFC mismatch` | See the box above. Fix the team, not the certificate. |
| `Certificate expired` | The FIEL is past `notAfter`. Renew it with SAT. |
| `Failed to generate PFX` | The `.key` could not be decrypted — almost always a wrong password. |
**Response:**
```json theme={null}
{
"success": true,
"message": "FIEL credentials stored successfully. Business registered with SAT sync enabled.",
"data": {
"rfc": "AAA010101AAA",
"expires_at": 1893456000000,
"expires_at_readable": "2029-12-31T00:00:00.000Z",
"serial_number": "00001000000504465028",
"sync_start_date": "2019-09-01",
"phone": "+5215512345678",
"registered": true,
"registered_at": 1743600000000
}
}
```
When `registered: true`, your RFC is connected to SAT and you can start downloading invoices immediately.
**Registration only runs when you send `phone`.** Without it the credentials are still stored, but the response comes back with `registered: false` and you must call `POST /v2/invoices/download/register` yourself. With `phone` present and registration failing (rare — usually a temporary SAT/PAC service issue) the message says so explicitly; retry with the same `register` endpoint.
Re-uploading a renewed FIEL is safe: the certificate fields are updated in place and the team's Descarga Masiva activation and download schedule are preserved.
***
### Connecting via API (PFX)
If you already have a PFX file (PKCS#12) — for example, one you generated programmatically or from a previous export — you can connect your FIEL directly using a JSON body instead of uploading `.cer` + `.key` files.
> **One team per RFC.** gigstack connects one RFC per team. If you need to manage multiple RFCs (e.g. for different legal entities), create a separate gigstack team for each RFC.
>
> **The same RFC check applies here.** The RFC inside the PFX must equal the `rfc` on the team being addressed — the `?team=` target under Connect, otherwise the API key's own team — or the call returns `400 "RFC mismatch"`. See [the box in Step 3](#step-3-—-upload-your-fiel): a company FIEL cannot be attached to a team created with an individual's RFC, and the only fix is a team whose RFC matches.
>
> This endpoint takes the registration phone from the team's `supportPhone` (falling back to a generic number), so there is no `phone` field to send.
```http theme={null}
POST /v2/invoices/download/pfx
Content-Type: application/json
```
```bash theme={null}
curl -X POST "https://api.gigstack.io/v2/invoices/download/pfx" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pfx": "BASE64_ENCODED_PFX_STRING",
"pfx_password": "your-pfx-password"
}'
```
**Parameters:**
| Field | Type | Required | Description |
| - | - | - | - |
| `pfx` | string | Yes | Base64-encoded PFX (PKCS#12) containing the FIEL certificate and private key |
| `pfx_password` | string | Yes | Password to decrypt the PFX |
**Response:**
```json theme={null}
{
"success": true,
"message": "FIEL credentials stored successfully. Business registered with Prodigia and SAT sync enabled.",
"data": {
"rfc": "AAA010101AAA",
"expires_at": 1893456000000,
"expires_at_readable": "2029-12-31T00:00:00.000Z",
"serial_number": "00001000000504465028",
"sync_start_date": "2019-09-01",
"registered": true,
"registered_at": 1743600000000
}
}
```
The sync start date is always set to the maximum allowed window (71 months back) — it is not configurable via this endpoint.
If `registered: false`, registration with SAT failed. Use `POST /v2/invoices/download/register` to retry.
***
## Downloading Invoices
Once activated and registered, you have two options: **scheduled daily sync** or **manual requests**.
***
### Option A — Scheduled Daily Sync
Set up a schedule and gigstack automatically downloads new invoices every day.
```http theme={null}
PUT /v2/invoices/download/schedule
```
```bash theme={null}
curl -X PUT "https://api.gigstack.io/v2/invoices/download/schedule" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"time": "21:00",
"download_types": ["received"],
"days_back": 1
}'
```
**Parameters:**
| Field | Type | Required | Description |
| - | - | - | - |
| `enabled` | boolean | Yes | Enable or disable the schedule |
| `time` | string | Yes | Run time in `HH:mm` format (America/Mexico\_City timezone) |
| `download_types` | array | Yes | `["issued"]`, `["received"]`, or `["issued", "received"]`. Must be non-empty when `enabled` is `true`; may be `[]` when turning the schedule off |
| `days_back` | integer | Yes | How many days back to look on each run (1–90) |
**Response:**
```json theme={null}
{
"success": true,
"message": "Scheduled download enabled successfully",
"data": {
"schedule": {
"enabled": true,
"time": "21:00",
"downloadTypes": ["received"],
"daysBack": 1,
"lastRunAt": null,
"lastRunStatus": null,
"lastRunError": null
}
}
}
```
To check your current schedule configuration and FIEL status:
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/download/schedule" \
-H "Authorization: Bearer YOUR_TOKEN"
```
***
### Option B — Manual Download Request
Submit a request for a specific date range. SAT processes it asynchronously.
```http theme={null}
POST /v2/invoices/download/request
```
```bash theme={null}
curl -X POST "https://api.gigstack.io/v2/invoices/download/request" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"start_date": "2024-01-01",
"end_date": "2024-01-31",
"rfc_type": "received"
}'
```
**Parameters:**
| Field | Type | Required | Description |
| - | - | - | - |
| `start_date` | string | Yes | Start of date range (`YYYY-MM-DD`) |
| `end_date` | string | Yes | End of date range (`YYYY-MM-DD`) |
| `rfc_type` | string | No | `"issued"` or `"received"` (default: `"received"`) |
| `request_type` | string | No | `"cfdi"` (XML files) or `"metadata"` (default: `"cfdi"`) |
| `invoice_type` | string | No | Filter by CFDI type: `I`, `E`, `P`, `N`, `T` |
| `invoice_status` | string | No | `"active"`, `"cancelled"`, or `"all"` (default: `"all"`) |
| `third_party_rfc` | string | No | Filter by counterparty RFC |
> **Date range:** the SAT limits each request to one month, so gigstack splits the range into one request per calendar month. Months that already have a pending request are not submitted again.
**Response:**
```json theme={null}
{
"success": true,
"message": "2 solicitud(es) creada(s) para 2 período(s) mensual(es)",
"data": {
"created": ["satreq_Ab12Cd34", "satreq_Ef56Gh78"],
"duplicates": [],
"chunks": 2,
"duplicate": false
}
}
```
`created` holds the ids of the new requests (use them with `GET /v2/invoices/download/status/:request_id`); `duplicates` holds ids of identical requests already pending; `duplicate` is `true` when every month was already pending. At most **10 manual requests per team per day** are accepted (`429 rate_limit_exceeded`).
***
### Tracking Request Status
Check the status of your download history — pending requests are updated in real time:
```http theme={null}
GET /v2/invoices/download/schedule/history
```
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/download/schedule/history" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Response:**
```json theme={null}
{
"success": true,
"data": {
"history": [
{
"id": "req_abc123",
"rfcType": "received",
"startDate": "2024-01-01",
"endDate": "2024-01-31",
"status": "completed",
"invoiceCount": 143,
"processedCount": 143,
"source": "manual",
"latestIssueDate": "2024-01-30",
"earliestIssueDate": "2024-01-02",
"createdAt": 1706745600000,
"completedAt": 1706832000000
}
]
}
}
```
**Status values:**
| Status | Meaning |
| - | - |
| `pending` | Request queued, being sent to SAT |
| `accepted` | SAT accepted the request |
| `processing` | SAT is generating the package |
| `completed` | Done — invoice count available |
| `failed` | SAT rejected (check `statusMessage`) |
| `expired` | Package expired before download |
***
### Checking a Single Request
```http theme={null}
GET /v2/invoices/download/status/:request_id
```
Returns the SAT status of one request (`status`: `pending`, `accepted`, `processing`, `completed`, `failed`, `expired`), `invoice_count`, and — once `completed` — `packages`, a list of `{ "id", "index" }`. Download each package with:
```http theme={null}
GET /v2/invoices/download/package/:package_id
```
The ZIP comes back **base64-encoded inside JSON** (`data.content`, with `content_type: "application/zip"` and `encoding: "base64"`), not as a binary download. Packages expire after a period set by the SAT, so download them promptly.
To fetch one CFDI's XML from the SAT without a bulk request, use `GET /v2/invoices/download/invoice/:uuid`.
***
## Preview a Range, Then Import Selectively
Reading CFDI metadata from the SAT is free; only downloading an XML is billed. Use that to see what a date range contains before paying for anything.
### 1. Preview
```http theme={null}
POST /v2/invoices/download/preview
```
```bash theme={null}
curl -X POST "https://api.gigstack.io/v2/invoices/download/preview" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "start_date": "2026-01-01", "end_date": "2026-08-31", "directions": ["received"] }'
```
`directions` defaults to received only. The call answers **`202`** with a job id (`satjob_…`, `status: "queued"`): one month takes about twelve seconds at the SAT, so the work runs in the background. Only one job may run per team at a time (`409` otherwise).
### 2. Follow the job
| Endpoint | Purpose |
| - | - |
| `GET /v2/invoices/download/jobs` | Your last 20 jobs, newest first, with progress and cost estimate |
| `GET /v2/invoices/download/jobs/:id` | One job, plus its month windows and each window's state |
| `POST /v2/invoices/download/jobs/:id/cancel` | Stop a job; it finishes the window in flight, then stops |
The CFDIs found are stored as rows in the `metadata` stage. List them with `GET /v2/invoices/sat?sync_state=metadata`.
### 3. Import the XMLs you want
```http theme={null}
POST /v2/invoices/download/import
```
**This is the billed call:** \$0.20 MXN per XML, charged once per CFDI.
```bash theme={null}
curl -X POST "https://api.gigstack.io/v2/invoices/download/import" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "uuids": ["6741A863-04BE-49FE-BB76-E1657AB6B7EA"], "confirm_cost_mxn": 0.2 }'
```
* Up to 500 UUIDs per call.
* The server always computes the cost itself. If you send `confirm_cost_mxn` and it does not match, the call returns `409` instead of charging a different amount. Above 100 invoices, `confirm_cost_mxn` is required.
* UUIDs that cannot be imported are listed in `skipped` with a reason: `not_found`, `wrong_team`, `already_imported`, `already_queued`, `is_nomina` (nómina XMLs contain employee data and are never downloadable) or `not_importable`.
The response reports `queued`, `estimated_cost_mxn` and `skipped`. Each XML that arrives triggers a `sat.invoice.synced` [webhook](/guides/webhooks).
***
## Sync Progress
```http theme={null}
GET /v2/invoices/download/progress
```
The SAT hands over your history in month-sized windows, starting at your sync start date. Until the backfill reaches the present, a query for recent dates succeeds but returns nothing — which looks exactly like having no invoices. This endpoint tells the two apart. The values live under `data.provider`, which is `null` until the first background refresh:
| Field | Meaning |
| - | - |
| `percent` | How much of the requested history has arrived |
| `covered_through` | Last date the backfill has reached |
| `months_remaining` | Roughly how much history is still pending |
| `current` | History is close enough to the present to be usable |
| `stalled` | The backfill has not advanced in over two days |
| `enabled` | Sync is active; when `false` it will not advance on its own |
| `eta_at` | Projected completion (epoch ms); omitted while `stalled` |
Pass `?refresh=true` to recompute now (takes a few seconds).
### Sync configuration and troubleshooting
* `GET /v2/invoices/download/schedule` — the current schedule plus setup status: `fiel_uploaded`, `registered`, `registered_at`, `sat_completed`, and FIEL certificate metadata (RFC, expiry, serial number).
* `PUT /v2/invoices/download/sync-period` — re-registers the team so the provider syncs from the earliest date allowed. The body is ignored; the start date is always computed by gigstack. Requires a team RFC, a prior registration, stored FIEL credentials and a phone number on the FIEL record.
* `POST /v2/invoices/download/enable-sync` — lower-level switch that turns automatic sync on. In most cases, configure the schedule (`PUT /v2/invoices/download/schedule`) instead.
* `GET /v2/invoices/download/debug` — FIEL, registration, schedule and SAT connectivity details for troubleshooting.
***
## Deactivation
To stop Descarga Masiva and remove billing:
```http theme={null}
POST /v2/invoices/download/deactivate
```
```bash theme={null}
curl -X POST "https://api.gigstack.io/v2/invoices/download/deactivate" \
-H "Authorization: Bearer YOUR_TOKEN"
```
Scheduled downloads stop immediately. If the feature was billed as an add-on, the charge is removed from your subscription (prorated). Any XML downloads already recorded in the current period are still billed at period end.
***
## Common Errors
| Error | Cause | Fix |
| - | - | - |
| `RFC mismatch` | Certificate RFC doesn't match your team RFC | Upload the FIEL that belongs to this team's RFC |
| `Certificate expired` | FIEL has expired | Renew your FIEL at SAT's portal |
| `Failed to decrypt private key` | Wrong password for the `.key` file | Use the password created when generating the FIEL |
| `Invalid certificate file format` | Wrong file type | Upload `.cer` and `.key` files directly from SAT — do not convert |
| `Team RFC not configured` | Team has no RFC | Set up your fiscal information in gigstack settings first |
| `No active subscription` | No paid plan | Activate a paid plan before using Descarga Masiva |
| `duplicate: true` | Same request already submitted | No action needed — the existing request is still processing |
| `rate_limit_exceeded` (`429`) | More than 10 manual download requests today (Mexico City time) | Wait until midnight Mexico City time, or rely on the scheduled daily sync |
| `billing_not_activated` (`403`) | Descarga Masiva is not activated for the team | Call `POST /v2/invoices/download/activate` first |
***
***
## Browsing Downloaded Invoices
Once invoices are downloaded and saved, retrieve them with:
### List SAT Invoices
```http theme={null}
GET /v2/invoices/sat
```
```bash theme={null}
curl "https://api.gigstack.io/v2/invoices/sat?direction=received&from=2025-01-01&to=2025-12-31&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `direction` | `issued` \| `received` | Filter by invoice direction |
| `status` | `Vigente` \| `Cancelado` | Filter by SAT status |
| `invoice_type` | `I` \| `E` \| `P` \| `N` \| `T` | Filter by CFDI type |
| `issuer_rfc` | string | Filter by issuer RFC |
| `receiver_rfc` | string | Filter by receiver RFC |
| `from` | `YYYY-MM-DD` | Issue date from (inclusive) |
| `to` | `YYYY-MM-DD` | Issue date to (inclusive) |
| `limit` | integer | Results per page, 1–100 (default: 20) |
| `starting_after` | string | UUID cursor for next page |
**Response:**
```json theme={null}
{
"success": true,
"data": [
{
"id": "6741A863-04BE-49FE-BB76-E1657AB6B7EA",
"uuid": "6741A863-04BE-49FE-BB76-E1657AB6B7EA",
"direction": "received",
"issuer": { "rfc": "AAA010101AAA", "name": "SOME SUPPLIER SA DE CV" },
"receiver": { "rfc": "BBB020202BBB", "name": "MI EMPRESA SA DE CV" },
"invoice_type": "I",
"subtotal": 103.44,
"total": 120.0,
"currency": "MXN",
"exchange_rate": null,
"issue_date": "2025-01-15",
"stamp_date": "2025-01-15T12:34:56",
"cancellation_date": null,
"status": "Vigente",
"pac_rfc": "SAT970701NN3",
"version": "4.0",
"certificate_number": "00001000000513342038",
"download_request_id": "satreq_abc123",
"has_xml": false,
"created_at": 1736900000000,
"updated_at": 1736900000000
}
],
"has_more": true,
"next": "6741A863-04BE-49FE-BB76-E1657AB6B7EA",
"count": 20
}
```
### Get a Single SAT Invoice
```http theme={null}
GET /v2/invoices/sat/:uuid
```
```bash theme={null}
curl "https://api.gigstack.io/v2/invoices/sat/6741A863-04BE-49FE-BB76-E1657AB6B7EA" \
-H "Authorization: Bearer YOUR_TOKEN"
```
***
### Retry an XML Download
```http theme={null}
POST /v2/invoices/sat/:uuid/retry-xml
```
Retries the XML download for a **received** SAT invoice stuck in processing or error. If the XML is already stored, it answers `200` with `message: "Invoice already has XML downloaded."` and charges nothing. A successful retry triggers a `sat.invoice.synced` webhook with `retried: true`.
### Generate a PDF
```http theme={null}
POST /v2/invoices/sat/:uuid/pdf
```
Generates a PDF from the stored XML of a received SAT invoice (the XML must have been downloaded). The result is cached. The response is **not** enveloped: it is `{ "pdf": "" }`.
***
### Import XMLs You Already Have
```http theme={null}
POST /v2/invoices/import
```
Uploads up to 50 stamped CFDI XMLs you already hold (from the SAT portal, a supplier, another system). **Free**, and it needs no FIEL or Descarga Masiva activation. Not to be confused with `POST /v2/invoices/download/import`, which fetches XMLs from the SAT and is billed.
Each XML is filed by your team's RFC:
* Your RFC is the **issuer** → stored as an invoice, readable with `GET /v2/invoices/:id`.
* Your RFC is the **receiver** → stored with your received SAT invoices (`GET /v2/invoices/sat`), already imported, so Descarga Masiva will never download or bill it.
* Neither → `rejected`, nothing is written.
Send each file as `xml` (text) or `content` (base64):
```bash theme={null}
curl -X POST "https://api.gigstack.io/v2/invoices/import" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "files": [ { "filename": "proveedor-a12.xml", "xml": "" } ] }'
```
Each result carries `action`: `created`, `completed` (filled a Descarga Masiva row that was missing its XML), `already_exists`, `conflict` (the UUID belongs to another account) or `rejected`, plus `direction` (`issued` / `received`). Received nómina XMLs are rejected because they contain employee personal data. Status is stored as `Vigente`: an XML cannot show a later cancellation.
***
## Full Endpoint Reference
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v2/invoices/download/activate/status` | Check activation status |
| `POST` | `/v2/invoices/download/activate` | Activate billing |
| `POST` | `/v2/invoices/download/deactivate` | Deactivate billing |
| `POST` | `/v2/invoices/download/fiel` | Upload FIEL credentials (.cer + .key, multipart) |
| `POST` | `/v2/invoices/download/pfx` | Upload FIEL via PFX (JSON body, alternative to .cer + .key) |
| `POST` | `/v2/invoices/download/register` | Manual SAT registration (fallback) |
| `GET` | `/v2/invoices/download/schedule` | Get schedule + FIEL status |
| `PUT` | `/v2/invoices/download/schedule` | Save schedule config |
| `GET` | `/v2/invoices/download/schedule/history` | Download request history |
| `POST` | `/v2/invoices/download/request` | Submit manual download request |
| `GET` | `/v2/invoices/download/status/:request_id` | Check specific request |
| `GET` | `/v2/invoices/download/package/:package_id` | Download a package (base64 ZIP inside JSON) |
| `GET` | `/v2/invoices/download/invoice/:uuid` | Get single CFDI from SAT |
| `GET` | `/v2/invoices/download/debug` | Debug SAT connectivity |
| `GET` | `/v2/invoices/sat` | List downloaded SAT invoices |
| `GET` | `/v2/invoices/sat/:uuid` | Get a single SAT invoice |
| `GET` | `/v2/invoices/download/progress` | SAT history sync progress |
| `POST` | `/v2/invoices/download/preview` | Preview a date range (free, runs in the background, `202`) |
| `GET` | `/v2/invoices/download/jobs` | List preview and import jobs |
| `GET` | `/v2/invoices/download/jobs/:id` | Get one job with its month windows |
| `POST` | `/v2/invoices/download/jobs/:id/cancel` | Stop a running job |
| `POST` | `/v2/invoices/download/import` | Download the XMLs of chosen CFDIs (billed) |
| `PUT` | `/v2/invoices/download/sync-period` | Re-register to sync from the earliest allowed date |
| `POST` | `/v2/invoices/download/enable-sync` | Turn on automatic sync |
| `POST` | `/v2/invoices/sat/:uuid/retry-xml` | Retry a stuck XML download |
| `POST` | `/v2/invoices/sat/:uuid/pdf` | Generate a PDF for a received SAT invoice |
| `POST` | `/v2/invoices/import` | Import CFDI XMLs you already hold, filed as issued or received (free) |
# Documents API Guide
Source: https://docs.gigstack.io/guides/documents
Integration guide for Documents
Some operations in this guide have Discovery handlers but no public gateway route yet: `POST /invoices/{id}/support-documents`, `GET /documents`, `POST /documents`, `GET /documents/{id}`, `PATCH /documents/{id}`, `DELETE /documents/{id}`, `POST /documents/{id}/analyze`, `DELETE /documents/{id}/link`. Check the availability notice on each API reference page before using them.
## Overview
The Documents API stores the metadata of supporting documents — contracts, proof of delivery, proof of payment, communications — and links them to the invoices, payments, receipts and clients they support. The SAT can ask for this evidence to validate an operation (*materialidad*); keeping it in gigstack, linked to the CFDI, means you can produce it when asked.
Base path: `https://api.gigstack.io/v2/documents`
Documents belong to the team and mode (`livemode`) of the API key that created them.
> **Uploading a file for a specific invoice, payment or client?** The `POST /invoices/{id}/support-documents` (and the equivalent client and payment endpoints) accept the file itself and link it in one call — see [Invoices](/guides/invoices#support-documents). The Documents API below records documents whose file is **already in storage**, and manages all of them in one place.
## Key Features
* **Link one document to many entities** - invoices, payments, receipts, clients
* **Compliance review state** - `pending_review`, `valid`, `requires_update`, `expired`, `rejected`
* **Validity window** - `validFrom` / `validUntil` for contracts and other time-bound evidence
* **AI extraction** - pull structured data out of a PDF or image
* **Soft delete** - deleted documents disappear from the API but are not destroyed
## Endpoints
### List Documents
```http theme={null}
GET /documents
```
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer | Page size (default **50**, max 100) |
| `cursor` | string | Id of the last document from the previous page |
| `document_type` | string | `contract`, `delivery_proof`, `payment_proof` or `communication` |
| `compliance_status` | string | `pending_review`, `valid`, `requires_update`, `expired` or `rejected` |
| `entity_type` | string | Only documents linked to this kind of entity: `invoice`, `payment`, `receipt`, `client` |
| `entity_id` | string | Only documents linked to this entity id (see note) |
Newest first; soft-deleted documents are excluded.
Note the nested shape and the pagination names, which differ from other list endpoints: the array is at `data.data`, and the cursor for the next page is `data.next_cursor` (pass it back as `cursor`).
```bash theme={null}
curl "https://api.gigstack.io/v2/documents?compliance_status=pending_review&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
```json theme={null}
{
"success": true,
"data": {
"data": [
{
"id": "doc_1234567890",
"document_type": "contract",
"name": "Contrato de servicios 2026 — Cliente ACME",
"compliance_status": "pending_review",
"linked_entities": [{ "entity_type": "client", "entity_id": "client_1234567890", "linked_at": 1767225600000 }],
"created_at": 1767225600000
}
],
"has_more": false,
"next_cursor": null
},
"timestamp": 1767225600000
}
```
`entity_id` is applied after the page is fetched, so it only narrows that page. If matches are sparse, use a larger `limit`.
### Create Document
```http theme={null}
POST /documents
```
Records a document whose file you have **already uploaded** to storage. This endpoint does not accept the file.
| Field | Type | Required | Description |
| - | - | - | - |
| `documentType` | string | yes | `contract`, `delivery_proof`, `payment_proof` or `communication` |
| `name` | string | yes | Display name |
| `fileUrl` | string | yes | URL of the uploaded file |
| `storagePath` | string | yes | Storage path of the uploaded file |
| `fileName` | string | yes | Original file name |
| `description` | string | no | |
| `fileSize` | number | no | Bytes |
| `mimeType` | string | no | e.g. `application/pdf` |
| `linkedEntities` | array | no | `[{ "entityType": "client", "entityId": "client_…" }]` |
| `validFrom` | integer | no | Validity start, epoch ms |
| `validUntil` | integer | no | Validity end, epoch ms |
| `tags` | array | no | Strings |
| `metadata` | object | no | Your own key-value data |
| `analyzeWithAI` | boolean | no | Run AI extraction after creating |
Request fields are **camelCase**; response fields are snake\_case — including the link objects, which you send as `{ "entityType", "entityId" }` and read back as `{ "entity_type", "entity_id", "linked_at" }`. Unknown top-level keys are rejected with `400`. `complianceStatus` cannot be set here — it always starts as `pending_review`.
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/documents \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"documentType": "contract",
"name": "Contrato de servicios 2026 — Cliente ACME",
"fileUrl": "https://storage.googleapis.com/gigstack-docs/team_123/contrato-acme.pdf",
"storagePath": "teams/team_123/documents/contrato-acme.pdf",
"fileName": "contrato-acme.pdf",
"mimeType": "application/pdf",
"validFrom": 1767225600000,
"validUntil": 1798761600000,
"linkedEntities": [{ "entityType": "client", "entityId": "client_1234567890" }]
}'
```
Answers `201` with the document in `data`.
The `file_url` you read back is not necessarily the one you sent: gigstack re-mints it as a Firebase download-token URL for the same object when it answers, so links no longer depend on a public storage ACL. It does not expire — but treat it as opaque and re-read the document rather than constructing or caching the link.
### Get Document
```http theme={null}
GET /documents/{id}
```
A document of another team, or a soft-deleted one, answers `404`.
### Update Document
```http theme={null}
PATCH /documents/{id}
```
Updates metadata and the compliance review. Only the fields you send are written. Accepted fields: `name`, `description`, `complianceStatus`, `complianceNotes`, `validFrom`, `validUntil`, `tags`, `metadata`. The file itself (`fileUrl`, `storagePath`, `fileName`, `documentType`) cannot be changed; sending those keys returns `400`.
```bash theme={null}
curl -X PATCH https://api.gigstack.io/v2/documents/doc_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"complianceStatus": "valid",
"complianceNotes": "Revisado por el área fiscal, cumple con el requisito de materialidad."
}'
```
### Link and Unlink
```http theme={null}
POST /documents/{id}/link
DELETE /documents/{id}/link
```
Both take the same body:
```json theme={null}
{ "entityType": "invoice", "entityId": "B0C3E4F2-1A2B-4C5D-9E8F-0123456789AB" }
```
`entityType` is `invoice`, `payment`, `receipt` or `client`. Linking twice to the same entity returns `400`; linking to an entity that does not exist or belongs to another team returns `404`. Unlinking a document that is not linked returns `400`; if the entity no longer exists the link is still removed. Note that this `DELETE` carries a request body.
### Analyze with AI
```http theme={null}
POST /documents/{id}/analyze
```
Runs AI extraction and stores the result on the document under `ai_extraction`. Only PDFs and PNG/JPEG/WEBP images can be analyzed; a scanned PDF with no text layer returns `400`. The request body is ignored.
### Delete Document
```http theme={null}
DELETE /documents/{id}
```
Soft delete: the document stops appearing in list and get responses and is unlinked from every entity. A second delete returns `404`.
## Errors
All errors use the standardized envelope (`success: false`, `error.code`, `error.message`, `timestamp`).
| Status | When |
| - | - |
| `400` | Body validation failure (including unknown keys), duplicate link, file not analyzable |
| `404` | Document not found, deleted, or of another team; link target not found |
| `500` | Unexpected failure |
## Related Resources
* [Invoices API](/guides/invoices#support-documents) - Upload a file for an invoice in one call
* [Clients API](/guides/clients) - Client support documents
* [Payments API](/guides/payments) - Payment support documents
# gigstack Connect API Guide
Source: https://docs.gigstack.io/guides/gigstack-connect
Integration guide for gigstack Connect
Access and manage resources across multiple teams within the same billing account. gigstack Connect enables authorized teams to work with resources from other teams seamlessly.
## Overview
gigstack Connect is a powerful feature that allows teams with special permissions to access and manage resources (clients, invoices, payments, etc.) across multiple teams that share the same billing account. This is ideal for franchises, multi-entity businesses, service providers managing multiple client accounts, and marketplace platforms that need to split payments between platform and merchants.
## Key Features
* **Multi-Team Access** - Access resources across teams
* **Payment Splitting** - Split marketplace payments between platform and merchants
* **Unified Management** - Manage multiple teams from one API key
* **Billing Account Scope** - Limited to teams in same billing account
* **Global Availability** - Works on all 35+ API endpoints
* **Seamless Integration** - Simple query parameter addition
* **Security Controls** - Permission-based access
## How It Works
Simply add the `team` query parameter to ANY endpoint to access another team's resources:
```bash theme={null}
# Standard request (your team)
GET /clients
# gigstack Connect request (another team)
GET /clients?team=team_xyz789
```
## Requirements
1. **gigstack Connect Enabled** - Your API key's team must be a master team (gigstack Connect enabled)
2. **Shared Billing** - Target team must share the same billing account
3. **Team Exists** - Target team must exist
4. **Plan Feature** - Your plan must include `multipleIssuerAccounts`
5. **API Key** - Use an API key. OAuth access tokens are bound to their own team; sending another team's id returns `403 Team mismatch with OAuth token`
## Choose the right RFC before you create the team
This is the single highest-cost mistake in a Connect integration, so it comes before the examples.
**One RFC ⇄ one team. Always.** A team's `rfc` is what binds it to a taxpayer, and there is no way to attach a second RFC to an existing team. If a partner manages ten merchants, that is ten teams.
**The RFC you pick at team-creation time is the RFC whose FIEL that team will be able to accept — and nothing else.** When the connected team later uploads its e-firma:
```http theme={null}
POST /v2/invoices/download/fiel?team=team_connected123
```
gigstack reads the RFC out of the `.cer` and compares it, character for character, to the `rfc` on the team named by `?team=`. Any difference is a hard `400`:
```json theme={null}
{
"success": false,
"message": "RFC mismatch",
"error": "FIEL certificate RFC (…) does not match your team RFC (…). Each RFC requires its own team."
}
```
Two things about that check that trip people up:
* **It is the *connected* team that is checked, not the master team.** The `?team=` parameter selects which team the credentials are attached to and which RFC they are validated against. A FIEL uploaded without `?team=` lands on the API key's own team — usually the master — and will be rejected unless the certificate happens to be the master's own.
* **There is no override.** No flag, no support toggle. If the RFCs differ, the only remedy is a team whose `rfc` matches the certificate.
### Use the business RFC, not the legal representative's
When a partner onboards a company, the RFC that should go on the team is **the company's** — the one the company invoices under and the one its FIEL is issued to. It is easy to instead capture the RFC of whoever is filling in the onboarding form: the director, the founder, the accountant. That RFC belongs to a *persona física* and its FIEL is a personal certificate; it will never match a company certificate.
A quick sanity check that catches almost every case:
| RFC length | Taxpayer type | Certificate you will receive |
| - | - | - |
| **12 characters** | Persona moral (company) | Company FIEL |
| **13 characters** | Persona física (individual) | Personal FIEL |
If the team's RFC is 13 characters and the merchant is a company, the team is wrong. Fix it before you ask them for their FIEL.
### Why this bites late
Nothing in the earlier steps complains. Team creation accepts any RFC. Clients, payments, services, receipts and webhooks all work. The mismatch only surfaces at FIEL upload — the last step of onboarding, after the merchant has already exported their certificate and typed their e-firma password. This has cost real partners days of repeated failed uploads before anyone compared the two RFCs.
**Do this instead:**
1. Confirm with the merchant which RFC they invoice under, and confirm their FIEL is issued to that same RFC.
2. Create the connected team with that RFC. On `POST /v2/teams` the field is **`tax_id`**; on `POST /v2/auth/signup` it is **`rfc`**. Both land on the team's internal `rfc`, which is what the FIEL check compares against.
3. Read the team back (`GET /v2/teams/{id}`, where it is returned as `tax_id`) and verify it before collecting any certificate.
4. Only then request the FIEL and upload it with `?team=` pointed at that team.
See [Descarga Masiva → Step 3](/guides/descarga-masiva#step-3-—-upload-your-fiel) for the full list of upload errors.
## Usage Examples
### Accessing Clients Across Teams
```bash theme={null}
# List clients from team_abc123
curl -X GET "https://api.gigstack.io/v2/clients?team=team_abc123&limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"
# Create client for team_xyz789
curl -X POST "https://api.gigstack.io/v2/clients?team=team_xyz789" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Cross-team Client",
"email": "client@example.com",
"tax_id": "ABC123456789"
}'
```
### Managing Invoices Across Teams
```bash theme={null}
# Create invoice for team_def456
curl -X POST "https://api.gigstack.io/v2/invoices/income?team=team_def456" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"automation_type": "payment",
"client": {"id": "client_123"},
"currency": "MXN",
"exchange_rate": 1.0,
"items": [{
"description": "Service for subsidiary",
"quantity": 1,
"unit_price": 1000.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"use": "P01",
"payment_form": "03",
"payment_method": "PUE"
}'
# Get invoice from team_ghi789
curl -X GET "https://api.gigstack.io/v2/invoices/income/invoice_123?team=team_ghi789" \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Processing Payments for Other Teams
```bash theme={null}
# Register payment for team_jkl012
curl -X POST "https://api.gigstack.io/v2/payments/register?team=team_jkl012" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_456"},
"automation_type": "pue_invoice",
"currency": "MXN",
"items": [{
"id": "service_789",
"quantity": 1
}],
"paid": true
}'
# List payments from team_mno345
curl -X GET "https://api.gigstack.io/v2/payments?team=team_mno345&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Managing Services Across Teams
```bash theme={null}
# Create service for team_pqr678
curl -X POST "https://api.gigstack.io/v2/services?team=team_pqr678" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Shared consulting service",
"sku": "SHARED-001",
"product_key": "80141503",
"unit_key": "E48",
"unit_price": 2000.00,
"taxes": [{"type": "IVA", "rate": 0.16}]
}'
# Update service in team_stu901
curl -X PUT "https://api.gigstack.io/v2/services/service_123?team=team_stu901" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"unit_price": 2500.00
}'
```
### Team Settings Management
```bash theme={null}
# Update settings for team_vwx234
curl -X PUT "https://api.gigstack.io/v2/teams/team_vwx234/settings?team=team_vwx234" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}'
# Add member to team_yz567
curl -X POST "https://api.gigstack.io/v2/teams/team_yz567/add-member?team=team_yz567" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"user_id": "user_890",
"role": "member"
}'
```
## Marketplace Payment Splitting
gigstack Connect enables marketplace platforms to automatically split payments between the platform (master team) and merchants (connect teams). This feature is only available for master teams with marketplace-enabled billing accounts.
### How Payment Splitting Works
When you register a payment with `transfer_data`, the system:
1. Validates that your team has marketplace permissions
2. Calculates the split based on the master percentage or custom\_price
3. Creates two separate payments (master and connect)
4. Assigns clients according to your configuration
5. Returns both payment IDs and split details
**Important:** The `team` and `livemode` fields are automatically extracted from your authentication token - you don't need to send them.
### Basic Marketplace Payment Split
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_MASTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {
"id": "client_1234567890"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [{
"description": "Marketplace transaction",
"quantity": 1,
"unit_price": 1000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"transfer_data": {
"master": 15,
"connect": "ABC123456789",
"master_to": "client",
"connect_to": "client"
}
}'
```
**Response:**
```json theme={null}
{
"message": "Split payment registered successfully",
"data": {
"split_reference": "split_abc123xyz",
"master_payment_id": "payment_master_123",
"connect_payment_id": "payment_connect_456",
"master_amount": 174.0,
"connect_amount": 986.0,
"total_amount": 1160.0,
"master_payment": {
"id": "payment_master_123",
"client": "client_1234567890",
"amount": 174.0,
"team": "team_master",
"split_role": "master"
},
"connect_payment": {
"id": "payment_connect_456",
"client": "client_1234567890",
"amount": 986.0,
"team": "team_connect",
"split_role": "connect"
},
"connect_team": null
}
}
```
### Creating a New Merchant Team
If the connect team doesn't exist (by tax ID), a new team will be created automatically:
> **The `transfer_data.connect` RFC becomes the new team's RFC permanently.** Auto-creation is the easiest place to get this wrong, because the RFC arrives buried in a payment payload rather than in a team form. Send the merchant's *business* RFC here — that team will only ever accept a FIEL issued to this exact value. See [Choose the right RFC](#choose-the-right-rfc-before-you-create-the-team).
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_MASTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [{
"description": "New merchant transaction",
"quantity": 1,
"unit_price": 2000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"transfer_data": {
"master": 10,
"connect": "NEWMERCH800101ABC",
"master_to": "client",
"connect_to": "master"
}
}'
```
**Response with new team:**
```json theme={null}
{
"message": "Split payment registered successfully",
"data": {
"split_reference": "split_xyz789abc",
"master_payment_id": "payment_master_789",
"connect_payment_id": "payment_connect_012",
"master_amount": 232.0,
"connect_amount": 2088.0,
"total_amount": 2320.0,
"master_payment": {
"id": "payment_master_789",
"client": "client_1234567890",
"amount": 232.0,
"team": "team_master",
"split_role": "master"
},
"connect_payment": {
"id": "payment_connect_012",
"client": "client_master_as_merchant",
"amount": 2088.0,
"team": "team_newmerch",
"split_role": "connect"
},
"connect_team": {
"id": "team_newmerch",
"tax_id": "NEWMERCH800101ABC",
"legal_name": "New Merchant SA de CV",
"is_newly_created": true,
"onboarding_url": "https://embeded.gigstack.pro/?sessionId=otpOnboarding_7Kd2Pq&c=193847"
}
}
}
```
Send the `onboarding_url` to the merchant to complete their account setup.
### Custom Item Configuration for Connect Payments
Customize how items appear in the connect team's payment and invoices:
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_MASTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [{
"description": "Platform marketplace transaction",
"quantity": 1,
"unit_price": 5000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"transfer_data": {
"master": 20,
"connect": "MERCHANT800101ABC",
"master_to": "client",
"connect_to": "client",
"connect_custom_config": {
"product_key": "01010101",
"unit_key": "E48",
"custom_description": "Professional consulting services rendered",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
}
]
}
}
}'
```
The connect payment will use the custom product key, unit key, description, and tax configuration instead of copying from the original items.
### Fixed Commission Pricing
Instead of percentage-based splits, charge a fixed commission fee using `custom_price`:
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_MASTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [{
"description": "Product sale through marketplace",
"quantity": 3,
"unit_price": 750.0,
"product_key": "43211500",
"unit_key": "H87",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"transfer_data": {
"master": 0,
"connect": "VENDOR123ABC",
"master_to": "connect",
"connect_to": "client",
"connect_custom_config": {
"custom_price": 75.00,
"custom_description": "Marketplace platform fee",
"product_key": "80141600"
}
}
}'
```
**Response with Fixed Commission:**
```json theme={null}
{
"message": "Split payment registered successfully",
"data": {
"split_reference": "split_xyz789",
"master_payment_id": "payment_master_456",
"connect_payment_id": "payment_connect_789",
"master_amount": 2535.00,
"connect_amount": 75.00,
"total_amount": 2610.00,
"used_custom_price": true
}
}
```
**Benefits of Fixed Pricing:**
* **Predictable fees**: Charge the same commission regardless of transaction size
* **Minimum guarantees**: Ensure a minimum platform fee on small transactions
* **Tiered pricing**: Implement different fixed fees for different service levels
* **Simple calculations**: Merchants know exactly what they'll pay
**Important Notes:**
* `custom_price` overrides the percentage calculation
* Connect gets the exact `custom_price` amount
* Master receives the remainder (total - custom\_price)
* The `master` percentage is ignored when using `custom_price`
### Client Assignment Strategies
**Strategy 1: Both payments to original client**
```json theme={null}
{
"transfer_data": {
"master": 15,
"connect": "ABC123456789",
"master_to": "client",
"connect_to": "client"
}
}
```
* Master payment: assigned to original client
* Connect payment: assigned to original client
* Use case: End customer pays both platform and merchant
**Strategy 2: Master to client, Connect to master**
```json theme={null}
{
"transfer_data": {
"master": 10,
"connect": "ABC123456789",
"master_to": "client",
"connect_to": "master"
}
}
```
* Master payment: assigned to original client
* Connect payment: master team becomes the client
* Use case: Platform pays merchant on behalf of customer
**Strategy 3: Master to connect, Connect to client**
```json theme={null}
{
"transfer_data": {
"master": 5,
"connect": "ABC123456789",
"master_to": "connect",
"connect_to": "client"
}
}
```
* Master payment: connect team becomes the client
* Connect payment: assigned to original client
* Use case: Merchant pays platform fee, customer pays merchant
**Strategy 4: Both cross-assigned**
```json theme={null}
{
"transfer_data": {
"master": 12,
"connect": "ABC123456789",
"master_to": "connect",
"connect_to": "master"
}
}
```
* Master payment: connect team becomes the client
* Connect payment: master team becomes the client
* Use case: Complex inter-company transactions
## Use Cases
### 1. Franchise Management
Manage multiple franchise locations from a central account:
```javascript theme={null}
// Get all clients across franchise locations
const franchises = ['team_location1', 'team_location2', 'team_location3']
const allClients = []
for (const franchise of franchises) {
const response = await fetch(`https://api.gigstack.io/v2/clients?team=${franchise}`, {
headers: {
Authorization: 'Bearer YOUR_TOKEN',
},
})
const data = await response.json()
allClients.push(...data.data)
}
console.log(`Total clients across all franchises: ${allClients.length}`)
```
### 2. Multi-Entity Corporation
Handle invoicing for different business entities:
```javascript theme={null}
// Create invoices for different entities
const entities = {
team_entity_mx: { currency: 'MXN', tax_rate: 0.16 },
team_entity_us: { currency: 'USD', tax_rate: 0.0 },
team_entity_ca: { currency: 'CAD', tax_rate: 0.13 },
}
for (const [teamId, config] of Object.entries(entities)) {
await fetch(`https://api.gigstack.io/v2/invoices/income?team=${teamId}`, {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
automation_type: 'payment',
client: { id: 'client_universal' },
currency: config.currency,
exchange_rate: 1.0,
items: [
{
description: 'International services',
quantity: 1,
unit_price: 1000.0,
product_key: '80141503',
unit_key: 'E48',
taxes: [
{
type: 'IVA',
rate: config.tax_rate,
},
],
},
],
use: 'P01',
payment_form: '03',
payment_method: 'PUE',
}),
})
}
```
### 3. Accounting Service Provider
Manage multiple client companies:
```javascript theme={null}
// Generate monthly reports for all managed companies
async function generateMonthlyReports(managedTeams) {
const reports = {}
for (const teamId of managedTeams) {
// Get invoices for the month
const invoices = await fetch(`https://api.gigstack.io/v2/invoices/income?team=${teamId}&limit=100`, {
headers: { Authorization: 'Bearer YOUR_TOKEN' },
}).then((r) => r.json())
// Get payments for the month
const payments = await fetch(`https://api.gigstack.io/v2/payments?team=${teamId}&limit=100`, {
headers: { Authorization: 'Bearer YOUR_TOKEN' },
}).then((r) => r.json())
reports[teamId] = {
total_invoiced: invoices.data.reduce((sum, inv) => sum + inv.total, 0),
total_collected: payments.data
.filter((p) => p.status === 'succeeded')
.reduce((sum, pay) => sum + pay.total, 0),
invoice_count: invoices.data.length,
payment_count: payments.data.length,
}
}
return reports
}
```
### 4. Consolidated Operations
Perform bulk operations across teams:
```javascript theme={null}
// Update service prices across all teams
async function updateServicePriceGlobally(serviceId, newPrice, teams) {
const results = []
for (const teamId of teams) {
try {
const response = await fetch(`https://api.gigstack.io/v2/services/${serviceId}?team=${teamId}`, {
method: 'PUT',
headers: {
Authorization: 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
unit_price: newPrice,
}),
})
results.push({
team: teamId,
success: response.ok,
status: response.status,
})
} catch (error) {
results.push({
team: teamId,
success: false,
error: error.message,
})
}
}
return results
}
```
### 5. Marketplace Platform
Process payments with automatic splitting between platform and merchants:
```javascript theme={null}
// Marketplace payment processing with split
async function processMarketplacePayment(
clientData,
items,
merchantTaxId,
platformFeePercentage = 10
) {
const response = await fetch('https://api.gigstack.io/v2/payments/register', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_MASTER_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
client: clientData,
automation_type: 'pue_invoice',
currency: 'MXN',
payment_form: '03',
items: items,
transfer_data: {
master: platformFeePercentage,
connect: merchantTaxId,
master_to: 'client',
connect_to: 'client',
},
metadata: {
marketplace_transaction: true,
merchant_id: merchantTaxId,
},
}),
})
const result = await response.json()
// Handle new merchant onboarding
if (result.data.connect_team?.is_newly_created && result.data.connect_team?.onboarding_url) {
console.log('New merchant created! Send onboarding link:', result.data.connect_team.onboarding_url)
}
return {
splitReference: result.data.split_reference,
platformPayment: result.data.master_payment_id,
merchantPayment: result.data.connect_payment_id,
platformAmount: result.data.master_amount,
merchantAmount: result.data.connect_amount,
needsOnboarding: result.data.connect_team?.is_newly_created || false,
onboardingUrl: result.data.connect_team?.onboarding_url || null,
}
}
// Example usage
const splitPayment = await processMarketplacePayment(
{ id: 'client_1234567890' },
[
{
description: 'Marketplace sale',
quantity: 1,
unit_price: 1000.0,
product_key: '80141503',
unit_key: 'E48',
taxes: [{ type: 'IVA', rate: 0.16 }],
},
],
'MERCHANT800101ABC',
15 // 15% platform fee
)
console.log(`Platform receives: $${splitPayment.platformAmount}`)
console.log(`Merchant receives: $${splitPayment.merchantAmount}`)
```
## Security and Permissions
### Access Control
* Only teams with gigstack Connect enabled can use this feature
* Target teams must be in the same billing account
* User permissions apply to cross-team operations
* Audit logs track all cross-team actions
### Permission Requirements
| Action | Required Role |
| - | - |
| Read resources | Viewer or higher |
| Create resources | Member or higher |
| Update resources | Member or higher |
| Delete resources | Admin or higher |
| Manage team settings | Admin or higher |
## Error Responses
Connect errors come from the authentication layer, before the endpoint runs. The body is a raw object with a `message` — not the standardized envelope.
### Not a Master Team (401)
```json theme={null}
{ "message": "Unauthorized, not a master team" }
```
**Solution:** Your team needs gigstack Connect enabled. Contact support.
### Team Not Found (404)
```json theme={null}
{ "message": "Team not found" }
```
**Solution:** Verify the team ID.
### No Matched Teams (401)
```json theme={null}
{ "message": "Unauthorized, no matched teams" }
```
**Solution:** The target team must share your master team's billing account.
### Plan Without Multiple Issuer Accounts (403)
```json theme={null}
{ "message": "Tu plan no incluye múltiples cuentas emisoras. Actualiza tu plan en https://app.gigstack.pro/memberships o ponte en contacto con soporte para operar sobre otros equipos." }
```
**Solution:** Move to a plan that includes `multipleIssuerAccounts`.
### OAuth Token Used for Another Team (403)
```json theme={null}
{ "message": "Team mismatch with OAuth token" }
```
**Solution:** Use an API key for cross-team requests.
## Best Practices
1. **Cache Team IDs** - Store frequently accessed team IDs
2. **Batch Operations** - Group operations by team for efficiency
3. **Error Handling** - Implement robust error handling for cross-team ops
4. **Audit Trail** - Log all cross-team operations
5. **Permission Checks** - Verify permissions before bulk operations
6. **Rate Limiting** - Be mindful of rate limits when accessing multiple teams
7. **Consistent Naming** - Use clear naming conventions for cross-team resources
## Performance Considerations
### Rate Limits
* The team credit limit is per team: documents issued for a connected team count against **that team's** `credit_limit`
* The 10-per-day manual SAT download limit is also per team
* See [Rate Limits](/guides/welcome#rate-limits) for the full list
### Optimization Tips
```javascript theme={null}
// Bad: Sequential requests
for (const team of teams) {
await fetchTeamData(team) // Slow!
}
// Good: Parallel requests
const promises = teams.map((team) => fetchTeamData(team))
const results = await Promise.all(promises) // Fast!
```
## Complete Endpoint Support
gigstack Connect works with ALL endpoints:
### Core Resources
* Supported: `/clients` - Client management
* Supported: `/services` - Service catalog
* Supported: `/invoices/income` - Invoice creation and management
* Supported: `/payments` - Payment processing
* Supported: `/teams` - Team configuration
* Supported: `/users` - User management
### Additional Operations
* Supported: `/clients/validate/{id}` - Validate client fiscal info
* Supported: `/clients/customerportal` - Customer portal access
* Supported: `/invoices/{id}/files` - Invoice file retrieval
* Supported: `/payments/{id}/paid` - Mark payments as paid
* Supported: `/payments/{id}/refund` - Process refunds
* Supported: `/teams/{id}/settings` - Team settings management
* Supported: `/teams/{id}/series` - Invoice series configuration
* Supported: All other endpoints in the API
## Getting Started
### Step 1: Verify gigstack Connect Status
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/teams" \
-H "Authorization: Bearer YOUR_TOKEN"
# Check response for gigstack_connect_enabled flag
```
### Step 2: List Available Teams
```bash theme={null}
# Your billing account teams are accessible
curl -X GET "https://api.gigstack.io/v2/teams" \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Step 3: Test Cross-Team Access
```bash theme={null}
# Try accessing another team's resources
curl -X GET "https://api.gigstack.io/v2/clients?team=team_other&limit=1" \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Step 4: Implement in Your Application
```javascript theme={null}
class GigstackMultiTeam {
constructor(apiKey) {
this.apiKey = apiKey
this.baseUrl = 'https://api.gigstack.io/v2'
}
async fetchFromTeam(endpoint, teamId, options = {}) {
const url = `${this.baseUrl}${endpoint}?team=${teamId}`
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers,
},
})
return response.json()
}
async createForTeam(endpoint, teamId, data) {
return this.fetchFromTeam(endpoint, teamId, {
method: 'POST',
body: JSON.stringify(data),
})
}
}
// Usage
const multiTeam = new GigstackMultiTeam('YOUR_TOKEN')
const clients = await multiTeam.fetchFromTeam('/clients', 'team_xyz789')
```
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Invoice Batches API Guide
Source: https://docs.gigstack.io/guides/invoice-batches
Integration guide for Invoice Batches
## Overview
An income invoice batch issues up to **1,000** CFDIs de ingreso from a single request. Each item of the batch is exactly the body you would send to [`POST /invoices/income`](/guides/invoices#create-income-invoice). gigstack validates every item right away, answers `202` with the batch, and stamps the accepted items in the background.
Use it when your company issues **one invoice per sale of its own** and has many of them at once: the day's orders from a store, a monthly subscription run, or a backlog after an outage.
Base path: `https://api.gigstack.io/v2/invoices/income/batch`
A batch goes through three steps:
1. **Create.** `POST /invoices/income/batch` with an `Idempotency-Key` header and `{ "invoices": [ … ] }`. Items that fail validation are listed in `rejected`; the rest are accepted.
2. **Follow.** Poll `GET /invoices/income/batch/{id}`, or subscribe a webhook to [`invoice_batch.completed`](#following-progress), until `status` is `completed`.
3. **Read the results.** Check [`result`](#batch-result), then page through `GET /invoices/income/batch/{id}/items` for each invoice's `uuid` or `error`.
## Key Features
* **Up to 1,000 invoices per request** - send more requests for more (see [Limits](#limits))
* **The single endpoint's rules, unchanged** - each item is validated and stamped by the same code as `POST /invoices/income`, so it succeeds or fails exactly as a single call would
* **Item-level rejections** - an invalid item is rejected with a reason, and the other items still go ahead
* **Safe retries at two levels** - the `Idempotency-Key` header makes a retried request return the same batch, and each item's `idempotency_key` makes sure an invoice is never issued twice (see [Idempotency](#idempotency))
* **Automatic retries** - an item that hits a temporary PAC failure is retried by gigstack, up to 6 attempts
* **A clear outcome** - a finished batch reports `result`: `completed`, `partially_completed` or `failed`
* **Polling or webhook** - `GET /invoices/income/batch/{id}` at any time, or `invoice_batch.completed` when it finishes
## When to Use It
| You want to… | Use |
| - | - |
| Issue one invoice now, and show the result to the person waiting for it | [`POST /invoices/income`](/guides/invoices#create-income-invoice). It answers with the stamped invoice |
| Issue dozens to thousands of invoices, where a few minutes' delay is fine | `POST /invoices/income/batch` |
| Invoice sales to the general public (*público en general*) who didn't ask for a CFDI | Usually neither. See below |
**Sales to the general public.** If most of your sales go to customers who don't ask for an invoice, you may not need one CFDI per sale at all. gigstack already produces the monthly **global invoice** (*factura global*) that groups those sales: record each sale as a [receipt](/guides/receipts), and the receipts nobody invoiced are included in the end-of-month global invoice automatically (see [End-of-Month Global Invoicing](/guides/invoices#end-of-month-global-invoicing) and [Global Invoices](/guides/catalogs/invoices_globals)). Use a batch for the customers who do need their own CFDI.
**Not supported by batches:**
* File uploads (CSV/XLSX). The batch takes JSON only.
* Egress invoices (*notas de crédito*) and payment complements (*complementos de pago*). Use [`POST /invoices/egress`](/guides/invoices#create-egress-invoice) and [`POST /invoices/payment`](/guides/invoices#create-payment-complement-complemento-de-pago) one by one.
* Returning files. `return_files` in an item is ignored; download the files later with [`GET /invoices/{id}/files`](/guides/invoices#get-invoice-files).
## Idempotency
A batch uses two keys, and they protect different things.
| Key | Where | Names | Protects against |
| - | - | - | - |
| `Idempotency-Key` | Request header, required | The **batch** | A retried request creating a second batch |
| `idempotency_key` | Each item, required | The **invoice** | The same invoice being issued twice, whichever request it came from |
### The batch key (`Idempotency-Key` header)
8-128 characters from `A-Z a-z 0-9 . _ : -`, for example `sales-2026-09-29-part-1`. The batch id is derived from your team, the credential's mode and this key, so:
* The **first** request with a key creates the batch: `202`.
* A later request with the **same key and the same body** returns that batch as it is now: `200`. Nothing is created again. If the first request died halfway through creating the batch, the retry finishes creating it.
* The **same key with a different body** is refused: `409 idempotency_key_reused`. Bodies are compared as sent, **including the order of keys**, so a retry must resend the exact same JSON.
* The same key used with a test key and with a live key names two different batches.
So if a `POST` times out or answers `500`, send it again unchanged with the same key.
### The invoice key (`idempotency_key` in each item)
This is the same `idempotency_key` that [`POST /invoices/income`](/guides/invoices#idempotency-and-safe-retries) accepts, for example your order id. It must be present in every item and unique within the batch (up to 256 characters; surrounding spaces are trimmed).
gigstack claims the key before it charges a credit or stamps, so:
* An invoice already issued under the key, by an earlier batch or by a single `POST /invoices/income`, is **not issued again**. The item ends `duplicate`, with the existing invoice's `uuid`, and no credit is charged.
* If the PAC's answer is lost, the next attempt sends the **same XML with the same folio**. The PAC either stamps it then, or reports the stamp it already made. It is never stamped twice. When the PAC cannot say, the item ends `needs_review` instead of risking a second CFDI.
* A rejection by the SAT frees the key, so you can fix the data and send the invoice again under the same key.
This is what makes it safe to **send a new batch with the items that failed**, or even to resend a whole batch under a new `Idempotency-Key`: everything already issued comes back as `duplicate`.
Keys are scoped to your team and to the credential's mode.
## Limits
* **1,000 invoices per request.** More is `400 too_many_items`. For 5,000 invoices, send five batches, each with its own `Idempotency-Key`. There is no limit on the number of batches.
* **Body size.** Keep the JSON body under 10 MB.
* **Throughput.** A team's items are stamped about 10 at a time across all its batches, so several batches from one team don't go faster than one. Expect up to roughly 10,000 invoices an hour, and less when other teams are stamping batches at the same time.
* **Credits.** Each stamped item consumes one credit, as a single call would. When the team's limit is reached, the remaining items fail with `credit_limit_reached`.
## Batch Status
| `status` | Meaning | What to do |
| - | - | - |
| `processing` | Some accepted items are still `queued` | Keep polling, or wait for the webhook |
| `completed` | Every accepted item has a final status | Read `result`: this status says nothing about how many invoices were issued |
A batch whose items were all rejected up front has nothing to stamp. It completes at once, usually already in the `202` response, with `result: "failed"`, and still sends `invoice_batch.completed`.
## Batch Result
`status: "completed"` only means there is nothing left to do. To know how it went, **read `result`, not `status`**. It is `null` while the batch is `processing`.
| `result` | When |
| - | - |
| `completed` | Every item was issued (`stamped`, or `duplicate` because it had been issued before), and nothing was rejected |
| `partially_completed` | At least one item was issued, and at least one wasn't (`failed`, `needs_review`, or rejected up front) |
| `failed` | No item was issued |
## Item Status
| `status` | Meaning | What to do |
| - | - | - |
| `queued` | Waiting for, or in, a stamping attempt. An item whose attempt failed for a temporary reason stays `queued` while it waits to be retried | Nothing |
| `stamped` | Issued by this batch. `uuid` and `invoice_id` are set | Nothing |
| `duplicate` | Already issued under this `idempotency_key` before. `uuid` and `invoice_id` name that invoice. Nothing was issued or charged again | Treat it as issued |
| `failed` | No invoice was issued; see `error` | Fix the cause and send the invoice again, in a new batch or on its own, with the **same** `idempotency_key` |
| `needs_review` | The PAC could not confirm whether the invoice was stamped. gigstack never retries it, so it can't be issued twice | Contact support with the batch id and the item's `index`. Don't resend it under a new key |
## Endpoints
### Create a Batch
```http theme={null}
POST /invoices/income/batch
```
**Headers:**
| Header | Required | Description |
| - | - | - |
| `Authorization` | Yes | `Bearer YOUR_TOKEN` |
| `Idempotency-Key` | Yes | 8-128 characters from `A-Z a-z 0-9 . _ : -`. See [The batch key](#the-batch-key-idempotency-key-header) |
| `Content-Type` | Yes | `application/json` |
**Body:** `{ "invoices": [ … ] }`, 1 to 1,000 items. Each item is the [`POST /invoices/income` body](/guides/invoices#create-income-invoice) with a required `idempotency_key`. `livemode`, `team` and `owner` in an item are ignored; they come from the credential.
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/income/batch \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Idempotency-Key: sales-2026-09-29-part-1" \
-H "Content-Type: application/json" \
-d '{
"invoices": [
{
"idempotency_key": "order-2026-09-000123",
"automation_type": "none",
"client": { "id": "client_1234567890" },
"currency": "MXN",
"use": "G03",
"payment_form": "04",
"payment_method": "PUE",
"items": [
{
"description": "Professional consulting services",
"quantity": 1,
"unit_price": 1000,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{ "type": "IVA", "rate": 0.16, "factor": "Tasa", "withholding": false }]
}
],
"send_email": true
},
{
"idempotency_key": "order-2026-09-000124",
"automation_type": "none",
"client": { "id": "client_0987654321" },
"currency": "MXN",
"use": "G03",
"payment_form": "03",
"payment_method": "PUE",
"items": [
{
"description": "Annual support plan",
"quantity": 1,
"unit_price": 2500,
"product_key": "81111811",
"unit_key": "E48",
"taxes": [{ "type": "IVA", "rate": 0.16, "factor": "Tasa", "withholding": false }]
}
]
}
]
}'
```
**Response (202):**
```json theme={null}
{
"success": true,
"data": {
"id": "ibatch_5d41402abc4b2a76b9719d911017c592",
"object": "invoice_batch",
"type": "income",
"livemode": true,
"status": "processing",
"result": null,
"total": 250,
"accepted": 248,
"rejected": [
{
"index": 17,
"idempotency_key": "order-2026-09-000140",
"error": { "code": "invalid_body", "message": "client_id: Unexpected field" }
},
{
"index": 42,
"idempotency_key": "order-2026-09-000123",
"error": { "code": "duplicate_idempotency_key", "message": "idempotency_key is already used by item 0" }
}
],
"counts": { "queued": 248, "stamped": 0, "failed": 0, "duplicate": 0, "needs_review": 0 },
"created_at": 1790780400000,
"completed_at": null
},
"timestamp": 1790780401250
}
```
A retry with the same `Idempotency-Key` and body answers `200` with the same batch, as it is now.
#### Rejected items
Each item is checked on its own when the batch is created, with no lookups. A rejected item is never stamped or charged, and doesn't stop the others. `index` is its position in `invoices`, from 0.
| `error.code` | Why |
| - | - |
| `invalid_item` | The item isn't a JSON object |
| `idempotency_key_required` | No `idempotency_key`, or an empty one |
| `invalid_idempotency_key` | The key is longer than 256 characters |
| `duplicate_idempotency_key` | An earlier item of this batch has the same key. The message names that item's index |
| `invalid_body` | The item fails the `POST /invoices/income` validation. The message lists each `path: message` |
What needs the database or the PAC (a client id that doesn't exist, a SAT rejection) can't be known up front. Those items are accepted and end `failed`.
### Get a Batch
```http theme={null}
GET /invoices/income/batch/{id}
```
Returns the batch in the same shape as the create response. `counts` fills in as items finish, and `result` and `completed_at` are set when the batch completes.
```bash theme={null}
curl https://api.gigstack.io/v2/invoices/income/batch/ibatch_5d41402abc4b2a76b9719d911017c592 \
-H "Authorization: Bearer YOUR_TOKEN"
```
A finished batch in which a few invoices failed:
```json theme={null}
{
"success": true,
"data": {
"id": "ibatch_5d41402abc4b2a76b9719d911017c592",
"object": "invoice_batch",
"type": "income",
"livemode": true,
"status": "completed",
"result": "partially_completed",
"total": 250,
"accepted": 248,
"rejected": ["..."],
"counts": { "queued": 0, "stamped": 245, "failed": 2, "duplicate": 1, "needs_review": 0 },
"created_at": 1790780400000,
"completed_at": 1790784000000
},
"timestamp": 1790784060000
}
```
A batch of another team, or of the other mode (a live batch read with a test key), answers `404 not_found`.
### List Batch Items
```http theme={null}
GET /invoices/income/batch/{id}/items
```
One entry per **accepted** item, in request order. Rejected items aren't listed; they are in the batch's `rejected`.
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer | Items per page, 1-500 (default **100**). Anything else is `400 invalid_limit` |
| `next` | string | Cursor from the previous page's `data.next`. An invalid one is `400 invalid_cursor` |
| `status` | string | Only items in this status: `queued`, `stamped`, `failed`, `duplicate` or `needs_review`. Anything else is `400 invalid_status` |
```bash theme={null}
curl "https://api.gigstack.io/v2/invoices/income/batch/ibatch_5d41402abc4b2a76b9719d911017c592/items?status=failed" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Response (200):** the array is at `data.data` and the cursor at `data.next`.
```json theme={null}
{
"success": true,
"data": {
"data": [
{
"index": 0,
"idempotency_key": "order-2026-09-000123",
"status": "stamped",
"invoice_id": "B0C3E4F2-1A2B-4C5D-9E8F-0123456789AB",
"uuid": "B0C3E4F2-1A2B-4C5D-9E8F-0123456789AB",
"error": null,
"attempts": 1
},
{
"index": 2,
"idempotency_key": "order-2026-09-000125",
"status": "failed",
"invoice_id": null,
"uuid": null,
"error": {
"code": "CFDI40147",
"message": "Error al timbrar la factura: CFDI40147 - El campo UsoCFDI no es válido [pcs_8f2a1c]"
},
"attempts": 1
}
],
"next": "2",
"has_more": true
},
"timestamp": 1790781000000
}
```
Pass `data.next` back as `next` while `data.has_more` is `true`. The cursor is the last `index` of the page, so pages never overlap or skip items while statuses change.
#### Item errors
`error` is set only on `failed` and `needs_review` items. Its `code` is what `POST /invoices/income` would have answered for the same body: a SAT/PAC code such as `CFDI40147`, or one of gigstack's (`SAT_NOT_CONNECTED`, `CSD_VALIDATION_ERROR`, `PAC_UNAVAILABLE`, `PAC_OUTCOME_UNKNOWN`, `STAMP_NEEDS_REVIEW`, …). Look it up in the [CFDI errors catalog](/guides/catalogs/cfdi_errors). Batches add a few of their own:
| `error.code` | Why | What to do |
| - | - | - |
| `credential_revoked` | The API key that created the batch was revoked or disabled before this item ran. The remaining items fail the same way | Send the failed items in a new batch with a valid key |
| `credit_limit_reached` | The team's credit limit was reached | Raise the limit, then send the failed items again |
| `http_` | The invoice endpoint answered that status without a code, for example `http_404` for a client or service id that doesn't exist | Read `message`, fix, and send again |
| `auth_unavailable`, `internal_error`, `idempotency_in_progress`, `PAC_UNAVAILABLE`, `PAC_OUTCOME_UNKNOWN` | A temporary failure that lasted through all 6 attempts | Send the item again later with the same `idempotency_key` |
`message` is at most 500 characters. Stamping messages are in Spanish, as on the single endpoint.
**Finding the invoices.** `invoice_id` reads the invoice with [`GET /invoices/income/{id}`](/guides/invoices#get-income-invoice), and [`GET /invoices/{id}/files`](/guides/invoices#get-invoice-files) returns its PDF and XML. You can also find an invoice by your own key with `GET /invoices/income?idempotency_key=…`.
## Following Progress
**Polling.** Poll `GET /invoices/income/batch/{id}` every 30-60 seconds. `counts.queued` goes down to `0` as items finish; the batch is done when `status` is `completed`.
**Webhook.** Subscribe a [webhook](/guides/webhooks) to `invoice_batch.completed`. It is sent once, when the batch completes, and carries the batch id, its counts and its `result`:
```json theme={null}
{
"id": "evt_9a2b7c4d1e6f3a8b",
"event": "invoice_batch.completed",
"created_at": 1790784000,
"data": {
"id": "ibatch_5d41402abc4b2a76b9719d911017c592",
"livemode": true,
"total": 250,
"accepted": 248,
"rejected": 2,
"counts": { "queued": 0, "stamped": 245, "failed": 2, "duplicate": 1, "needs_review": 0 },
"result": "partially_completed"
}
}
```
The delivery is signed with the webhook's `secret` (see [Verifying Signatures](/guides/webhooks#verifying-signatures)), but it is sent **only once and never retried**. If your endpoint is down at that moment, you miss it. Use the webhook to react quickly, and keep a slow poll (for example every 10 minutes) as a fallback. Note that `data.rejected` is a **count** here, while on the batch object `rejected` is the list.
Each invoice the batch stamps also sends the usual `invoice.created` event, like any other invoice. Items that fail don't send `invoice.failed`, so read the items to find them.
## End-to-End Example
```bash theme={null}
TOKEN="YOUR_TOKEN"
BASE="https://api.gigstack.io/v2/invoices/income/batch"
KEY="sales-2026-09-29-part-1"
# 1. Create the batch. If this times out, run it again unchanged: same key, same body.
BATCH_ID=$(curl -s -X POST "$BASE" \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" \
-H "Content-Type: application/json" \
--data-binary @invoices.json | jq -r '.data.id')
# 2. Items rejected up front: fix them and send them in another batch.
curl -s "$BASE/$BATCH_ID" -H "Authorization: Bearer $TOKEN" | jq '.data.rejected'
# 3. Wait until the batch completes (or react to the invoice_batch.completed webhook).
while true; do
BATCH=$(curl -s "$BASE/$BATCH_ID" -H "Authorization: Bearer $TOKEN")
echo "$BATCH" | jq -c '.data.counts'
[ "$(echo "$BATCH" | jq -r '.data.status')" = "completed" ] && break
sleep 60
done
echo "$BATCH" | jq -r '.data.result' # completed, partially_completed or failed
# 4. List the items that have no invoice.
NEXT=""
while true; do
PAGE=$(curl -s "$BASE/$BATCH_ID/items?status=failed&limit=500${NEXT:+&next=$NEXT}" -H "Authorization: Bearer $TOKEN")
echo "$PAGE" | jq -r '.data.data[] | "\(.index)\t\(.idempotency_key)\t\(.error.code)\t\(.error.message)"'
[ "$(echo "$PAGE" | jq -r '.data.has_more')" = "true" ] || break
NEXT=$(echo "$PAGE" | jq -r '.data.next')
done
# 5. Fix those invoices and send them in a new batch (new Idempotency-Key),
# keeping each invoice's own idempotency_key so nothing can be issued twice.
```
## Response Objects
### Batch
| Field | Type | Description |
| - | - | - |
| `id` | string | `ibatch_` + 32 hex characters. Derived from team, mode and `Idempotency-Key` |
| `object` | string | Always `invoice_batch` |
| `type` | string | Always `income` |
| `livemode` | boolean | Mode of the credential that created the batch |
| `status` | string | `processing` or `completed`. See [Batch Status](#batch-status) |
| `result` | string \| null | `completed`, `partially_completed` or `failed` once completed, `null` before. See [Batch Result](#batch-result) |
| `total` | integer | Invoices in the request |
| `accepted` | integer | Invoices that passed validation and are processed |
| `rejected` | object\[] | `{ index, idempotency_key, error: { code, message } }` for each item refused up front. See [Rejected items](#rejected-items) |
| `counts` | object | Accepted items by status: `queued`, `stamped`, `failed`, `duplicate`, `needs_review`. They add up to `accepted` |
| `created_at` | integer | Epoch ms |
| `completed_at` | integer \| null | When the last item finished, epoch ms |
### Item
| Field | Type | Description |
| - | - | - |
| `index` | integer | Position in the request's `invoices`, from 0 |
| `idempotency_key` | string | The item's key, trimmed |
| `status` | string | See [Item Status](#item-status) |
| `invoice_id` | string \| null | For `GET /invoices/income/{id}`. Set for `stamped` and `duplicate` |
| `uuid` | string \| null | Folio fiscal. Set for `stamped` and `duplicate` |
| `error` | object \| null | `{ code, message }` for `failed` and `needs_review`. See [Item errors](#item-errors) |
| `attempts` | integer | Stamping attempts so far |
## Error Handling
The batch endpoints use the standardized envelope. **Branch on `error.code`**, not on the message.
```json theme={null}
{
"success": false,
"error": {
"code": "idempotency_key_reused",
"message": "This Idempotency-Key was already used with a different body. Use a new key for a different batch."
},
"timestamp": 1790780400000
}
```
Authentication failures (`401`, and the `403` for a revoked key or a plan without API access) come from the authentication layer with a raw `{ "message": … }` body; see [Authentication errors](/guides/welcome#authentication-errors).
| Status | `error.code` | Endpoints | When |
| - | - | - | - |
| `400` | `invalid_request_body` | `POST /invoices/income/batch` | `Idempotency-Key` missing or malformed |
| `400` | `invalid_body` | `POST /invoices/income/batch` | The body isn't `{ "invoices": [ … ] }` with at least one item |
| `400` | `too_many_items` | `POST /invoices/income/batch` | More than 1,000 items |
| `400` | `invalid_limit` | `GET /{id}/items` | `limit` isn't an integer 1-500 |
| `400` | `invalid_status` | `GET /{id}/items` | `status` isn't one of the item statuses |
| `400` | `invalid_cursor` | `GET /{id}/items` | `next` isn't a valid cursor |
| `401` | `unauthorized` | All | Missing or invalid credential |
| `403` | `forbidden` | `POST /invoices/income/batch` | User-scoped token (MCP, dashboard) without `editor` permission on invoices |
| `404` | `not_found` | `GET /{id}`, `GET /{id}/items` | No such batch in your team and mode |
| `409` | `idempotency_key_reused` | `POST /invoices/income/batch` | The `Idempotency-Key` was already used with a different body. Use a new key |
| `500` | `internal_server_error` | All | Unexpected failure. Retrying the `POST` with the same key and body is safe |
A problem with **one item** is never an HTTP error: it is in `rejected`, or on the item as `failed`.
**gigstack Connect.** A master team's API key with the `multipleIssuerAccounts` feature can create a batch for a connected team with `?team=`. The batch belongs to that team: read it and its items with the same `team` parameter, or they answer `404`. See [gigstack Connect](/guides/gigstack-connect).
## Best Practices
1. **Use your order id as each item's `idempotency_key`.** It is what guarantees an order is invoiced once, across batches, retries and single calls.
2. **Retry a request with the same `Idempotency-Key` and the exact same body.** Use a new key only for a different set of invoices.
3. **Resend failed items in a new batch, with their original `idempotency_key`.** Don't mint new keys for them, or you lose the protection against issuing twice.
4. **Never resend a `needs_review` item under a new key.** Contact support; it may already be stamped.
5. **Split large volumes into batches of up to 1,000.** Send them one after another; a team's items are stamped at the same pace however many batches it has.
6. **Try it with a test key first.** Test batches and live batches are separate.
7. **Read `result`, not `status`,** and list the `failed` items when it isn't `completed`.
8. **Don't rely on the webhook alone.** It is sent once and never retried; keep a slow poll as a fallback.
## Related Resources
* [Invoices API](/guides/invoices) - The single `POST /invoices/income`, whose body each item uses, and its error handling
* [Receipts API](/guides/receipts) - Sales that end up in the monthly global invoice
* [Webhooks API](/guides/webhooks) - Subscribe to `invoice_batch.completed`
* [Platform Payouts API](/guides/platform-payouts) - For marketplaces invoicing on behalf of their providers, not for your own sales
* [CFDI Errors Reference](/guides/catalogs/cfdi_errors) - Stamping errors you may see in an item's `error`
* [Test Mode](/guides/welcome#test-mode)
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Invoices API Guide
Source: https://docs.gigstack.io/guides/invoices
Integration guide for Invoices
Some operations in this guide have Discovery handlers but no public gateway route yet: `GET /invoices/payment/{id}`, `POST /invoices/{id}/support-documents`. Check the availability notice on each API reference page before using them.
Create, manage, and cancel CFDI 4.0 compliant invoices with full SAT integration. The Invoices API handles the complete invoice lifecycle including creation, stamping, cancellation, and payment complements.
## Overview
The Invoices API provides comprehensive invoicing capabilities with Mexican tax compliance. Create PUE (single payment) or PPD (partial payments) invoices, manage payment complements, and handle cancellations with SAT.
## Key Features
* **CFDI 4.0 Compliance** - Full SAT specification support
* **Automatic Stamping** - Real-time SAT integration
* **Payment Complements** - PPD invoice management
* **Cancellation Support** - SAT-compliant cancellation process
* **Global Invoices** - Monthly global invoice generation
* **File Generation** - Automatic PDF and XML creation
* **Draft Invoices** - Prepare, preview, and approve invoices before stamping
* **Batches** - Up to 1,000 income invoices per request, stamped in the background ([Invoice Batches](/guides/invoice-batches))
* **Safe retries** - An `idempotency_key` makes sure a retried request never issues a second CFDI
* **Automation Options** - Flexible workflow automation
## Endpoints
### List CFDI Errors
```http theme={null}
GET /invoices/errors
```
Retrieve a comprehensive catalog of CFDI error codes with descriptions, explanations, and solutions. This endpoint is essential for implementing proper error handling and providing meaningful feedback when invoice operations fail.
**Query Parameters:**
* `code` (string) - Filter by exact error code (e.g., CFDI140223)
* `q` (string) - Search across code, description, explanation, and solution
* `type` (string) - Filter by error type: `invoice`, `receiver`, `sender`, `unknown`
* `limit` (integer, 1-100) - Number of results per page (default: 50)
* `page` (integer) - Page number for pagination (default: 1)
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/errors?type=receiver&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"data": [
{
"code": "CFDI140223",
"description": "El campo Rfc del receptor no es valido",
"explanation": "The RFC (tax ID) provided for the receiver does not meet the validation requirements or format specified by SAT",
"solution": "Verify that the receiver's RFC is correct, properly formatted (13 characters for individuals, 12 for legal entities), and matches SAT's registered information",
"type": "receiver"
}
],
"total": 1,
"page": 1,
"limit": 20,
"message": "CFDI errors retrieved successfully",
"timestamp": "2025-12-19T10:30:00.000Z"
}
```
For complete documentation on the CFDI errors endpoint, see the [CFDI Errors Reference](/guides/catalogs/cfdi_errors).
### List Income Invoices
```http theme={null}
GET /invoices/income
```
Retrieve a paginated list of income invoices with filtering capabilities.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `next` | string | Pagination cursor for next page |
| `team` | string | gigstack Connect: Target team ID |
| `order_by` | string | Field to sort by (e.g., `created_at`) |
| `sort` | string | Sort direction (`asc`, `desc`) |
| `created_gte` | integer | Filter by creation date (greater than or equal to timestamp) |
| `created_lte` | integer | Filter by creation date (less than or equal to timestamp) |
| `client_id` | string | Filter by the gigstack client ID (e.g., `client_id=client_xxx`) |
| `tax_id` | string | Filter by the client's tax ID / RFC (e.g., `tax_id=PEGJ800101ABC`) |
| `metadata.{key}` | string | Filter by metadata field using dot notation (e.g., `metadata.order_id=ORD-123`) |
| `metadata_{key}` | string | Filter by metadata field using underscore notation (e.g., `metadata_order_id=ORD-123`) |
| `page` | integer | Page number for pagination when using metadata filters (default: 1) |
**Metadata Filtering:**
You can filter invoices by any metadata key using either dot notation or underscore notation. Both formats are equivalent:
```bash theme={null}
# Dot notation
GET /invoices/income?metadata.order_id=ORD-123
# Underscore notation (alternative)
GET /invoices/income?metadata_order_id=ORD-123
```
Metadata filtering uses Typesense search for efficient querying without requiring Firestore indexes. When using metadata filters, pagination is controlled via the `page` parameter instead of the `next` cursor.
**Example Requests:**
```bash theme={null}
# Basic listing
curl -X GET "https://api.gigstack.io/v2/invoices/income?limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by creation date range
curl -X GET "https://api.gigstack.io/v2/invoices/income?created_gte=1700000000000&created_lte=1710000000000" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by metadata (dot notation)
curl -X GET "https://api.gigstack.io/v2/invoices/income?metadata.order_id=ORD-123" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by metadata (underscore notation)
curl -X GET "https://api.gigstack.io/v2/invoices/income?metadata_project_id=PROJ-456" \
-H "Authorization: Bearer YOUR_TOKEN"
# Multiple metadata filters with pagination
curl -X GET "https://api.gigstack.io/v2/invoices/income?metadata.department=sales&metadata.region=north&page=2&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Invoices retrieved successfully",
"data": [
{
"uuid": "invoice_1234567890",
"client": {
"id": "client_1234567890",
"name": "Juan Perez Garcia",
"tax_id": "PEGJ800101ABC"
},
"folio_number": 123,
"series": "A",
"invoice_type": "I",
"total": 1160.0,
"subtotal": 1000.0,
"taxes": 160.0,
"currency": "MXN",
"payment_method": "PUE",
"status": "valid",
"created_at": 1677651234,
"stamp": {
"stamp_at": 1677651234,
"sello": "ABC123..."
}
}
],
"has_more": false,
"total_results": 1
}
```
### Create Income Invoice
```http theme={null}
POST /invoices/income
```
Create a new income invoice with optional automation.
**Automation Types:**
* `payment` - Create invoice with payment automation
* `none` - No automation, create invoice only
**Request Body:**
```json theme={null}
{
"automation_type": "payment",
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"exchange_rate": 1.0,
"items": [
{
"description": "Consulting services",
"quantity": 2,
"unit_price": 1000.0,
"product_key": "80141503",
"unit_key": "E48",
"unit_name": "Servicio",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"factor": "Tasa",
"withholding": false
}
]
}
],
"use": "P01",
"payment_form": "03",
"payment_method": "PUE",
"series": "A",
"folio_number": 123,
"send_email": true,
"emails": ["client@example.com"]
}
```
**Example Request with Client Search:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/income \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"automation_type": "payment",
"client": {
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"auto_create": true,
"safety_check": false
},
"name": "Juan Perez Garcia",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601"
},
"currency": "MXN",
"exchange_rate": 1.0,
"items": [
{
"description": "Professional services",
"quantity": 1,
"unit_price": 5000.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}
],
"use": "G03",
"payment_form": "03",
"payment_method": "PUE"
}'
```
To issue many invoices at once, send the same bodies to [`POST /invoices/income/batch`](/guides/invoice-batches), up to 1,000 per request.
#### Idempotency and safe retries
Send your own identifier for the invoice, for example your order id, as `idempotency_key`. gigstack claims the key before it charges a credit or stamps, so sending the same request again can never issue a second CFDI or charge twice. What a request with a used key gets:
| Situation | Answer | What to do |
| - | - | - |
| The invoice already exists | `400`, `message.code: "INVALID_INVOICE"`, `message.duplicate: true`, `message.uuid` | Treat it as success; read the invoice by its `uuid` |
| Another request with the key is still being processed | `409`, `error.code: "idempotency_in_progress"`, `retryable: true` | Retry later with the same key |
| The PAC's answer to an earlier attempt was lost | The retry resends **the same XML and folio**; the PAC stamps it once or returns the stamp it already made | Nothing; you get the invoice (`200`) or its error |
| The PAC can't confirm an earlier attempt | `409`, `message.code: "STAMP_NEEDS_REVIEW"` | Contact support. Don't retry under a new key |
| An earlier attempt was rejected (bad data, SAT rejection) | The key is free again; the request is processed normally | Fix the body and send it with the same key |
The duplicate answer looks like this:
```json theme={null}
{
"message": {
"error": "Error al timbrar la factura: Ya existe un comprobante con la misma idempotencia (uuid: 0f8fad5b-d9cb-469f-a165-70867728950e)",
"code": "INVALID_INVOICE",
"providerMessage": "Ya existe un comprobante con la misma idempotencia (uuid: 0f8fad5b-d9cb-469f-a165-70867728950e)",
"retryable": false,
"duplicate": true,
"uuid": "0f8fad5b-d9cb-469f-a165-70867728950e"
}
}
```
Keys are scoped to your team and to the credential's mode. You can look an invoice up by its key with `GET /invoices/income?idempotency_key=…`. Without an `idempotency_key` none of this protection applies (see [503](#503-—-pac-unavailable-or-outcome-unknown)).
### Get Income Invoice
```http theme={null}
GET /invoices/income/{id}
```
Retrieve a specific income invoice by ID.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/invoices/income/invoice_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Create Egress Invoice
```http theme={null}
POST /invoices/egress
```
Create a new egress invoice (credit note / nota de crédito).
**Request Body:**
```json theme={null}
{
"automation_type": "none",
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"exchange_rate": 1.0,
"items": [
{
"description": "Credit for returned goods",
"quantity": 1,
"unit_price": 500.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
],
"use": "G02",
"payment_form": "03",
"payment_method": "PUE",
"related_documents": [
{
"relationship": "01",
"documents": ["A1B2C3D4-0000-0000-0000-000000000000"]
}
]
}
```
**`payment_method`** is optional and defaults to `PUE`, which is how every egress invoice was
stamped before the field was accepted. Set it to `PPD` when the credit note applies to a
partial/deferred-payment invoice. With `PPD`, SAT requires `payment_form` to be `99`
("Por definir"); any other value you send is overridden to `99` rather than rejected.
A `PPD` egress invoice is **not** a valid target for a payment complement — `ppd_invoice_id`
on `POST /payments` only accepts income invoices.
### List Egress Invoices
```http theme={null}
GET /invoices/egress
```
Retrieve a paginated list of egress invoices (credit notes) with filtering capabilities.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `next` | string | Pagination cursor for next page |
| `team` | string | gigstack Connect: Target team ID |
| `order_by` | string | Field to sort by (e.g., `created_at`) |
| `sort` | string | Sort direction (`asc`, `desc`) |
| `created_gte` | integer | Filter by creation date (greater than or equal to timestamp) |
| `created_lte` | integer | Filter by creation date (less than or equal to timestamp) |
| `client_id` | string | Filter by the gigstack client ID (e.g., `client_id=client_xxx`) |
| `tax_id` | string | Filter by the client's tax ID / RFC (e.g., `tax_id=PEGJ800101ABC`) |
| `metadata.{key}` | string | Filter by metadata field using dot notation (e.g., `metadata.order_id=ORD-123`) |
| `metadata_{key}` | string | Filter by metadata field using underscore notation (e.g., `metadata_order_id=ORD-123`) |
| `page` | integer | Page number for pagination when using metadata filters (default: 1) |
**Metadata Filtering:**
You can filter egress invoices by any metadata key using either dot notation or underscore notation. Both formats are equivalent:
```bash theme={null}
# Dot notation
GET /invoices/egress?metadata.order_id=ORD-123
# Underscore notation (alternative)
GET /invoices/egress?metadata_order_id=ORD-123
```
Metadata filtering uses Typesense search for efficient querying without requiring Firestore indexes. When using metadata filters, pagination is controlled via the `page` parameter instead of the `next` cursor.
**Example Requests:**
```bash theme={null}
# Basic listing
curl -X GET "https://api.gigstack.io/v2/invoices/egress?limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by creation date range
curl -X GET "https://api.gigstack.io/v2/invoices/egress?created_gte=1700000000000&created_lte=1710000000000" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by metadata (dot notation)
curl -X GET "https://api.gigstack.io/v2/invoices/egress?metadata.expense_category=travel" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by metadata (underscore notation)
curl -X GET "https://api.gigstack.io/v2/invoices/egress?metadata_vendor_id=VEND-789" \
-H "Authorization: Bearer YOUR_TOKEN"
# Multiple metadata filters with pagination
curl -X GET "https://api.gigstack.io/v2/invoices/egress?metadata.department=IT&metadata.approved=true&page=2&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Get Invoice Files
```http theme={null}
GET /invoices/{id}/files
```
Retrieve XML and PDF files for an invoice.
**Query Parameters:**
* `file_type` (string) - Type of file: "pdf", "xml" (optional, returns both if not specified)
* `team` (string) - gigstack Connect: Target team ID
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/invoice_1234567890/files?file_type=pdf" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Files retrieved successfully",
"data": [
{ "content": "JVBERi0xLjcKJeLjz9MK...", "filename": "Yx7Kp2Lm9Qw4Rt6Bn1Vc.pdf", "type": "application/pdf" }
]
}
```
### Cancel Invoice
```http theme={null}
DELETE /invoices/{id}
```
Cancel an invoice with SAT.
**Request Body:**
```json theme={null}
{
"motive": "02",
"substitution_uuid": "12345678-1234-1234-1234-123456789012"
}
```
**Cancellation Motives:**
* `01` - Comprobante emitido con errores con relacion (requires substitution\_uuid)
* `02` - Comprobante emitido con errores sin relacion
* `03` - No se llevo a cabo la operacion
* `04` - Operacion nominativa relacionada en una factura global
**Example Request:**
```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/invoices/invoice_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"motive": "02"
}'
```
### Create Payment Complement (Complemento de Pago)
```http theme={null}
POST /invoices/payment
```
Stamps a CFDI type **P** (Pagos 2.0) that records one or more payments against **PPD** invoices. Each entry in `complements[].data` is one payment, and each payment lists the PPD invoices it pays in `related_documents`.
The SAT fixes some values, so you do not send them: the comprobante currency (`XXX`), the receptor's `UsoCFDI` (`CP01`) and the line concept. The series defaults to the team's payments series.
**Request Body:**
| Field | Type | Required | Description |
| - | - | - | - |
| `client` | object | yes | Client reference (`{ "id": "client_…" }`) or inline client, as in the other invoice endpoints |
| `complements` | array | yes | Normally one entry: `{ "type": "pago", "data": [ …payments ] }`. `type` is optional and defaults to `pago` |
| `date` | integer | no | Comprobante date, epoch ms. Defaults to now |
| `series` | string | no | Overrides the payments series |
| `folio_number` | integer | no | Stamp with this exact folio |
| `related_documents` | array | no | CFDI relations at the comprobante level |
| `idempotency_key` | string | no | Prevents creating the same complement twice |
| `return_files` | boolean | no | Include base64 XML and PDF in the response |
| `send_email`, `ignore_emails`, `emails` | | no | Email delivery options |
| `invoice_pdf_notes`, `metadata` | | no | Notes printed on the PDF; your own key-value data |
**Each payment (`complements[].data[]`):**
| Field | Required | Description |
| - | - | - |
| `payment_form` | yes | SAT `c_FormaPago`, e.g. `03` (transfer) |
| `date` | yes | Payment date and time, ISO 8601 |
| `currency` | yes | Payment currency |
| `exchange` | no | Exchange rate to MXN (default 1) |
| `related_documents` | yes | The PPD invoices this payment pays |
**Each related document:** `uuid` (the PPD invoice's folio fiscal), `amount` (paid now), `installment` (1 for the first payment against that invoice), `last_balance` (balance before this payment) and `currency`; optionally `exchange`, `series`, `folio_number` and `taxes`.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/payment \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": { "id": "client_1234567890" },
"complements": [{
"type": "pago",
"data": [{
"payment_form": "03",
"date": "2026-07-24T12:00:00",
"currency": "MXN",
"exchange": 1,
"related_documents": [{
"uuid": "A1B2C3D4-E5F6-7890-ABCD-1234567890AB",
"amount": 116,
"installment": 1,
"last_balance": 116,
"currency": "MXN",
"taxes": [{ "base": 100, "rate": "0.16", "factor": "Tasa", "type": "IVA", "withholding": false, "inclusive": false }]
}]
}]
}]
}'
```
**Response:** `200` with `{ "message": "Payment complement created", "data": { …invoice } }`. This endpoint returns a raw body, not the standardized envelope. A body that fails validation returns `400` with `{ "message": "Invalid body", "errors": [ … ] }`.
List and read complements with `GET /invoices/payment` and `GET /invoices/payment/{id}`, which work like the income-invoice list and get.
### Create Transfer Invoice (Traslado with Carta Porte)
```http theme={null}
POST /invoices/transfer
GET /invoices/transfer
GET /invoices/transfer/{id}
```
Stamps a CFDI type **T** (traslado) with the **Carta Porte 3.1** complement, for moving your own goods by road (autotransporte). It is the document that travels with the truck.
The SAT fixes the comprobante, so you do not send it: currency `XXX`, `Total` 0, `UsoCFDI` `S01`, no payment method. There are no `items`; the CFDI concepts are built from `carta_porte.Mercancias.Mercancia`. The series defaults to `T`.
Only teams that stamp through gigstack's own PAC (CSD uploaded in gigstack) can use this endpoint.
**Request Body:**
| Field | Type | Required | Description |
| - | - | - | - |
| `client` | object | yes | Receptor, as in the other invoice endpoints. For goods you move yourself, this is usually your own company |
| `carta_porte` | object | yes | Carta Porte data, using the SAT attribute names from CartaPorte31.xsd (see below) |
| `series` | string | no | Defaults to `T` |
| `folio_number` | number | no | Custom folio |
| `date` | number | no | Unix epoch milliseconds |
| `idempotency_key` | string | no | Safe retries |
| `related_documents` | array | no | CFDI relations |
| `send_email`, `emails`, `metadata`, `return_files` | | no | Same as the other invoice endpoints |
**`carta_porte`** (numbers can be numbers or numeric strings):
| Field | Required | Notes |
| - | - | - |
| `TranspInternac` | yes | `No`, or `Sí` with `EntradaSalidaMerc` and `PaisOrigenDestino` |
| `TotalDistRec` | yes | Total km |
| `Ubicaciones[]` | yes, 2+ | `TipoUbicacion` (`Origen`/`Destino`), `RFCRemitenteDestinatario`, `FechaHoraSalidaLlegada` (`YYYY-MM-DDTHH:mm:ss`), `Domicilio` (`Estado`, `Pais`, `CodigoPostal` required, SAT keys). Every `Destino` needs `DistanciaRecorrida`; it is dropped from the `Origen` |
| `Mercancias` | yes | `PesoBrutoTotal`, `UnidadPeso` (e.g. `KGM`), `NumTotalMercancias`, `Mercancia[]` with `BienesTransp`, `Descripcion`, `Cantidad`, `ClaveUnidad`, `PesoEnKg` |
| `Autotransporte` | yes | `PermSCT`, `NumPermisoSCT`, `IdentificacionVehicular` (`ConfigVehicular`, `PlacaVM`, `AnioModeloVM`), `Seguros` (`AseguraRespCivil`, `PolizaRespCivil`), optional `Remolques[]` (max 2) |
| `FiguraTransporte[]` | yes | `TipoFigura`, `RFCFigura`; for `01` (operator) send `NumLicencia` |
**Example:**
```json theme={null}
{
"client": { "id": "client_1234567890" },
"carta_porte": {
"TranspInternac": "No",
"TotalDistRec": 120,
"Ubicaciones": [
{
"TipoUbicacion": "Origen",
"RFCRemitenteDestinatario": "EKU9003173C9",
"NombreRemitenteDestinatario": "ESCUELA KEMPER URGATE",
"FechaHoraSalidaLlegada": "2026-10-06T09:00:00",
"Domicilio": { "Pais": "MEX", "CodigoPostal": "42501", "Estado": "HID" }
},
{
"TipoUbicacion": "Destino",
"RFCRemitenteDestinatario": "EKU9003173C9",
"NombreRemitenteDestinatario": "ESCUELA KEMPER URGATE",
"FechaHoraSalidaLlegada": "2026-10-06T13:00:00",
"DistanciaRecorrida": 120,
"Domicilio": { "Pais": "MEX", "CodigoPostal": "03020", "Estado": "CMX" }
}
],
"Mercancias": {
"PesoBrutoTotal": 12.5,
"UnidadPeso": "KGM",
"NumTotalMercancias": 1,
"Mercancia": [
{ "BienesTransp": "50202203", "Descripcion": "Bebida embotellada", "Cantidad": 10, "ClaveUnidad": "XBO", "PesoEnKg": 12.5 }
]
},
"Autotransporte": {
"PermSCT": "TPAF01",
"NumPermisoSCT": "0X2XTXZ0X5X0X3X2X1X0",
"IdentificacionVehicular": { "ConfigVehicular": "VL", "PlacaVM": "ABC1234", "AnioModeloVM": 2022, "PesoBrutoVehicular": 3 },
"Seguros": { "AseguraRespCivil": "SEGUROS SA", "PolizaRespCivil": "123456" }
},
"FiguraTransporte": [
{ "TipoFigura": "01", "RFCFigura": "VAAM130719H60", "NombreFigura": "OPERADOR", "NumLicencia": "a234567890" }
]
}
}
```
A `400` lists the fields that failed, including the Carta Porte rules (for example `carta_porte.Ubicaciones[1].DistanciaRecorrida is required on a Destino`). PAC rejections come back with the PAC's message.
### Search Invoices
```http theme={null}
GET /invoices/search?q=…
```
Full-text, typo-tolerant search across client name, email, invoice UUID, description and metadata.
| Parameter | Type | Description |
| - | - | - |
| `q` | string | **Required.** Search text (`query` is accepted as an alias; `q` wins) |
| `limit` | integer | Results per page (default 10, max 100) |
| `page` | integer | Page number (default 1) |
| `fields` | string | Comma-separated fields to include |
```bash theme={null}
curl "https://api.gigstack.io/v2/invoices/search?q=Juan%20P%C3%A9rez&limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"
```
The response carries `data` plus `found` (total matches), `page` and `per_page`; there is no cursor. Search must be enabled for your team — otherwise the call returns `400` with `error.code: missing_typesense_key`. A missing `q` returns `400 missing_query`.
### Support Documents
```http theme={null}
POST /invoices/{id}/support-documents
GET /invoices/{id}/support-documents
```
Attach the evidence the SAT may request to validate an invoice — contracts, proof of delivery, proof of payment. The same two endpoints exist for clients (`/clients/{id}/support-documents`) and payments (`/payments/{id}/support-documents`).
Upload as `multipart/form-data`:
| Field | Required | Description |
| - | - | - |
| `file` | yes | PDF or image (PNG, JPG, WEBP), up to 10 MB |
| `documentType` | yes | `contract`, `delivery_proof`, `payment_proof`, `communication`, `payment_confirmation` or `subscription_info` |
| `name` | no | Display name |
| `description` | no | Free text |
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/invoice_1234567890/support-documents \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@contrato-acme.pdf" \
-F "documentType=contract" \
-F "name=Contrato de servicios 2026"
```
Answers `201` with the stored document in `data` (`id`, `document_type`, `file_url`, `compliance_status: "pending_review"`, …) in the standardized envelope. `file_url` is a Firebase download-token URL — it does not expire, but treat it as opaque and re-read the document instead of building the link yourself. `GET` lists the invoice's documents, newest first. An unknown invoice returns `404`. To review, link or analyze documents independently of one invoice, use the [Documents API](/guides/documents).
### End-of-Month Global Invoicing
```http theme={null}
POST /invoices/eom/run
```
Manually triggers the end-of-month process that groups your pending receipts into global invoices. Three preconditions:
* A **live** API key. Test keys get `403` (`error.code: operation_not_allowed`).
* It must be the **last calendar day of the month** in `America/Mexico_City`. Any other day returns `400` (`operation_not_allowed`), with today's date and the next eligible date in `error.details`.
* It must be **before 23:00** in `America/Mexico_City`. From 23:00 the automatic end-of-month run takes over, and manual calls return `400` (`operation_not_allowed`).
The body is ignored. The call returns as soon as the run is triggered and does not wait for it to finish:
```json theme={null}
{
"success": true,
"message": "The end-of-month global invoicing process has been triggered successfully.",
"data": { "global_invoice_time": "31/01/2026 23:59:00" },
"timestamp": 1767225600000
}
```
If the downstream process rejects the trigger, the call returns `502` (`external_service_error`) and nothing is invoiced.
***
## Draft Invoices (Pre-Facturas)
Draft invoices allow you to prepare and review an invoice before stamping it with SAT. This is useful for approval workflows, client review, or building invoices incrementally over time.
A draft follows this lifecycle:
1. **Create** a draft with partial or complete data.
2. **Update** the draft as needed (add items, set client, change payment method).
3. **Preview** the draft to generate a PDF with a "Sin Validez Fiscal" watermark.
4. **Stamp** the draft to finalize it into a real CFDI invoice.
Drafts are stored in the `invoices` collection with a `draft: true` flag and do not consume credits until stamped.
### Create Draft
```http theme={null}
POST /invoices/draft
```
Create a new draft invoice. Only `invoice_type` is required at creation; all other fields can be added later via update.
**Request Body:**
```json theme={null}
{
"invoice_type": "I",
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"items": [
{
"description": "Consulting services",
"quantity": 1,
"unit_price": 1000.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}
],
"use": "G03",
"payment_form": "03",
"payment_method": "PUE"
}
```
**Minimal Request (just the type):**
```json theme={null}
{
"invoice_type": "I"
}
```
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/draft \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"invoice_type": "I",
"client": {"id": "client_1234567890"},
"currency": "MXN",
"items": [{
"description": "Web development",
"quantity": 10,
"unit_price": 2500.00,
"product_key": "81111500",
"unit_key": "HUR",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"use": "G03",
"payment_form": "03",
"payment_method": "PUE"
}'
```
**Response (201):**
```json theme={null}
{
"message": "Draft created successfully",
"data": {
"id": "draft_abc123",
"draft": true,
"invoice_type": "I",
"client": {
"id": "client_1234567890",
"name": "Juan Perez Garcia"
},
"currency": "MXN",
"items": [...],
"status": "draft",
"created_at": 1709090576567
}
}
```
### List Drafts
```http theme={null}
GET /invoices/draft
```
Retrieve a paginated list of draft invoices.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `next` | string | Pagination cursor for next page |
| `invoice_type` | string | Filter by invoice type: `I` (income) or `E` (egress) |
| `client_id` | string | Filter by client ID |
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/invoices/draft?limit=20&invoice_type=I" \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Get Draft
```http theme={null}
GET /invoices/draft/{id}
```
Retrieve a specific draft by ID. If a preview PDF has been generated, it is included in the response.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/invoices/draft/draft_abc123 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Update Draft
```http theme={null}
PUT /invoices/draft/{id}
```
Update an existing draft. Only the fields included in the request body are merged; omitted fields remain unchanged.
**Example Request:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/invoices/draft/draft_abc123 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"payment_method": "PPD",
"payment_form": "99",
"items": [{
"description": "Updated service line",
"quantity": 5,
"unit_price": 3000.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}]
}'
```
### Delete Draft
```http theme={null}
DELETE /invoices/draft/{id}
```
Permanently delete a draft and its associated preview files.
**Example Request:**
```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/invoices/draft/draft_abc123 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Stamp Draft (Finalize)
```http theme={null}
POST /invoices/draft/{id}/stamp
```
Finalize a draft into a real CFDI invoice. This stamps the invoice with SAT, assigns a folio, and removes the draft document.
The draft must have all required fields before stamping:
* `client` with valid fiscal data
* At least one item
* `use` (CFDI use code)
* `payment_form`
* `payment_method`
* `currency`
**Optional Request Body:**
```json theme={null}
{
"send_email": true,
"return_files": true
}
```
* `send_email` (boolean) - Set to `false` to suppress email delivery. Default: `true`.
* `return_files` (boolean) - Set to `true` to include base64-encoded XML and PDF in the response.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/draft/draft_abc123/stamp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"return_files": true}'
```
**Response (200):**
```json theme={null}
{
"message": "Invoice created from draft",
"data": {
"uuid": "12345678-1234-1234-1234-123456789012",
"folio_number": 456,
"series": "A",
"total": 29000.00,
"status": "valid",
"stamp": {
"stamp_at": 1709090576567,
"sello": "ABC123..."
},
"files": {
"xml": "PD94bWwgdmVyc2lvbj...",
"pdf": "JVBERi0xLjQK..."
}
}
}
```
**Error: Incomplete draft (400):**
```json theme={null}
{
"message": "Draft is incomplete — a client and at least one item are required to stamp"
}
```
**Error: Credit limit reached (429):**
```json theme={null}
{
"message": "Team credit limit reached",
"error": "No remaining credits",
"credit_limit": 100,
"used_credits": 100
}
```
### Preview Draft
```http theme={null}
POST /invoices/draft/{id}/preview
```
Generate a preview PDF for the draft. The PDF includes a "Sin Validez Fiscal" watermark and uses a placeholder UUID (`PREFACTURA-0000-0000-0000-SINVALIDEZ`). The preview is saved to the draft's `files` subcollection.
The draft must have at least a client and one item.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/draft/draft_abc123/preview \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Response (200):**
```json theme={null}
{
"message": "Preview generated successfully",
"data": {
"pdf_base64": "JVBERi0xLjQK...",
"draft_id": "draft_abc123",
"watermark": true
}
}
```
### Draft Workflow Example
A typical approval workflow using drafts:
```bash theme={null}
# 1. Create a draft with basic information
curl -X POST https://api.gigstack.io/v2/invoices/draft \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"invoice_type": "I", "client": {"id": "client_123"}, "currency": "MXN"}'
# 2. Add items and payment details
curl -X PUT https://api.gigstack.io/v2/invoices/draft/DRAFT_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": [{"description": "Service", "quantity": 1, "unit_price": 5000, "product_key": "80141503", "unit_key": "E48", "taxes": [{"type": "IVA", "rate": 0.16}]}],
"use": "G03",
"payment_form": "03",
"payment_method": "PUE"
}'
# 3. Generate a preview PDF for client approval
curl -X POST https://api.gigstack.io/v2/invoices/draft/DRAFT_ID/preview \
-H "Authorization: Bearer YOUR_TOKEN"
# 4. Once approved, stamp it into a real CFDI
curl -X POST https://api.gigstack.io/v2/invoices/draft/DRAFT_ID/stamp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"return_files": true}'
```
***
## Invoice Structure
### Invoice Types
* **I** - Ingreso (Income)
* **E** - Egreso (Expense / Credit note)
* **P** - Pago (Payment complement)
* **T** - Traslado (Transfer of goods, with Carta Porte)
* **N** - Nomina (Payroll)
### Payment Methods
* **PUE** - Pago en Una sola Exhibicion (Single payment)
* **PPD** - Pago en Parcialidades o Diferido (Partial or deferred payment)
### Payment Forms (Formas de Pago)
| Code | Description |
| - | - |
| **01** | Efectivo |
| **02** | Cheque nominativo |
| **03** | Transferencia electronica de fondos |
| **04** | Tarjeta de credito |
| **28** | Tarjeta de debito |
| **99** | Por definir |
## Complex Invoice Examples
### Invoice with Multiple Items and Taxes
```json theme={null}
{
"automation_type": "payment",
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"exchange_rate": 1.0,
"items": [
{
"description": "Consulting services - Phase 1",
"quantity": 10,
"unit_price": 1000.0,
"product_key": "80141503",
"unit_key": "HUR",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
}
]
},
{
"description": "Software license",
"quantity": 1,
"unit_price": 5000.0,
"product_key": "81111500",
"unit_key": "H87",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
}
]
}
],
"use": "G03",
"payment_form": "03",
"payment_method": "PUE"
}
```
### Invoice with Withholding Taxes
```json theme={null}
{
"automation_type": "payment",
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"exchange_rate": 1.0,
"items": [
{
"description": "Professional services",
"quantity": 1,
"unit_price": 10000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
},
{
"type": "ISR",
"rate": 0.1,
"withholding": true
},
{
"type": "IVA",
"rate": 0.106667,
"withholding": true
}
]
}
],
"use": "G03",
"payment_form": "03",
"payment_method": "PUE"
}
```
### PPD Invoice (Partial Payments)
```json theme={null}
{
"automation_type": "none",
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"exchange_rate": 1.0,
"items": [
{
"description": "Large project - Total amount",
"quantity": 1,
"unit_price": 100000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
],
"use": "G03",
"payment_form": "99",
"payment_method": "PPD"
}
```
### Global Invoice
```json theme={null}
{
"automation_type": "none",
"client": {
"legal_name": "PUBLICO EN GENERAL",
"tax_id": "XAXX010101000",
"tax_system": "616",
"address": { "zip": "06600" }
},
"currency": "MXN",
"exchange_rate": 1.0,
"global": {
"periodicity": "04",
"months": "01",
"year": 2024
},
"items": [
{
"description": "Ventas del periodo",
"quantity": 1,
"unit_price": 50000.0,
"product_key": "01010101",
"unit_key": "ACT",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
],
"use": "S01",
"payment_form": "01",
"payment_method": "PUE"
}
```
The receiver of a global invoice is the general public (`XAXX010101000`, regime `616`, use `S01`, your own postal code). See [Global Invoices](/guides/catalogs/invoices_globals) for the periodicity and month codes.
## Related Documents
### Creating Related Invoices
```json theme={null}
{
"related_documents": [
{
"relationship": "04",
"documents": ["12345678-1234-1234-1234-123456789012"]
}
]
}
```
**Relationship Types:**
* `01` - Nota de credito de los documentos relacionados
* `02` - Nota de debito de los documentos relacionados
* `03` - Devolucion de mercancia sobre facturas o traslados previos
* `04` - Sustitucion de los CFDI previos
* `07` - CFDI por aplicacion de anticipo
## Common Scenarios
### 1. Simple Sale Invoice
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/income \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"automation_type": "payment",
"client_id": "client_1234567890",
"currency": "MXN",
"exchange_rate": 1.0,
"items": [{
"description": "Product sale",
"quantity": 1,
"unit_price": 1000.00,
"product_key": "01010101",
"unit_key": "H87",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"use": "G01",
"payment_form": "03",
"payment_method": "PUE"
}'
```
### 2. Service Invoice with Email
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/income \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"automation_type": "payment",
"client_id": "client_1234567890",
"currency": "MXN",
"exchange_rate": 1.0,
"items": [{
"id": "service_1234567890",
"quantity": 1
}],
"use": "G03",
"payment_form": "03",
"payment_method": "PUE",
"send_email": true,
"emails": ["client@example.com", "accounting@example.com"]
}'
```
### 3. USD Invoice with Exchange Rate
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/income \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"automation_type": "payment",
"client_id": "client_1234567890",
"currency": "USD",
"exchange_rate": 18.50,
"items": [{
"description": "International services",
"quantity": 1,
"unit_price": 100.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"use": "G03",
"payment_form": "03",
"payment_method": "PUE",
"exports": "02"
}'
```
## Best Practices
1. **Use idempotency keys** - Prevent duplicate invoices by including unique identifiers.
2. **Validate clients first** - Ensure the client's fiscal data (RFC, tax system, address) is correct before creating an invoice.
3. **Configure email settings** - Set up BCC addresses for your accounting department.
4. **Use correct payment methods** - Use PUE for immediate single payments and PPD for partial or deferred payments.
5. **Include all required taxes** - Apply IVA, ISR, and IEPS as applicable to each line item.
6. **Set proper CFDI use** - Match the CFDI use code to the client's tax requirements.
7. **Keep series organized** - Use different series for different invoice types or business units.
8. **Use drafts for review** - For high-value invoices, use the draft workflow to generate a preview before stamping.
## Related Resources
* [CFDI Errors Reference](/guides/catalogs/cfdi_errors) - Comprehensive error code catalog
* [Clients API](/guides/clients) - Manage invoice recipients
* [Services API](/guides/services) - Configure invoice items
* [Payments API](/guides/payments) - Process invoice payments
* [Teams API](/guides/teams) - Configure invoice settings
## Error Handling
When working with invoices, you may encounter various CFDI-specific error codes. Use the [CFDI Errors endpoint](/guides/catalogs/cfdi_errors) to look up detailed explanations and solutions for any error codes you receive.
### At a glance
| Status | When | Retry the same request? | What you should do |
| - | - | - | - |
| `400` | Body validation, invalid currency, or the **SAT rejected the document's data** | No — it will fail identically | Fix the payload. Read `code` and look it up in the [CFDI errors catalog](/guides/catalogs/cfdi_errors). |
| `401` | Missing/invalid token | No | Re-authenticate. |
| `403` | Livemode mismatch, or the resource belongs to another team | No | Use a key in the matching mode, or the right `?team=`. |
| `404` | Invoice, draft, client or service not found | No | Check the id. |
| `409` | A concurrent request is creating the same client or service (`search` + `auto_create`), or another request with the same `idempotency_key` is in progress (`idempotency_in_progress`) | **Yes**, after a short backoff | Wait and retry; the other request is mid-flight. |
| `409` | `STAMP_NEEDS_REVIEW`: the PAC can't confirm whether an earlier attempt with this `idempotency_key` stamped | No | Contact support. See below. |
| `412` | **SAT not connected** / CSD not valid | No, until fixed | Finish the SAT connection. See below. |
| `422` | The document cannot be operated on at all — e.g. cancelling an imported invoice | No | See below. |
| `429` | **Team credit limit reached** | No, until raised | See below. |
| `500` | Unhandled server error | Maybe | Report with the `[pcs_…]` process id if present. |
| `502` | An upstream service returned an error (e.g. the PDF renderer) | Yes, with backoff | See below. |
| `503` | `PAC_UNAVAILABLE`: **the PAC was unavailable**, the document was never evaluated | **Yes**, with backoff | See below. |
| `503` | `PAC_OUTCOME_UNKNOWN`: **the PAC's answer was lost**, the CFDI may exist | **Only with an `idempotency_key`** (`retryable: true`) | See below. |
### Two error shapes
CFDI failures use a dedicated shape:
```json theme={null}
{
"error": "Human-readable message, in Spanish, ending in an optional [pcs_…] process id",
"code": "CFDI40147",
"providerMessage": "The PAC's untouched original text",
"retryable": false
}
```
* `code` — the SAT/PAC error code, or one of gigstack's own (`SAT_NOT_CONNECTED`, `PAC_UNAVAILABLE`, `PAC_OUTCOME_UNKNOWN`, `STAMP_NEEDS_REVIEW`, `CSD_VALIDATION_ERROR`, `STAMPING_ERROR`, …).
* `providerMessage` — present only on `400` stamping rejections. It is the PAC's verbatim text; log it, it names the offending field and value.
* `retryable` — `true` for `503` `PAC_UNAVAILABLE`, and for `503` `PAC_OUTCOME_UNKNOWN` only when the request carried an `idempotency_key`. Treat it as authoritative: nothing else is worth retrying unchanged.
* `duplicate` and `uuid` — only on the `400` for an `idempotency_key` whose invoice already exists (see [Idempotency and safe retries](#idempotency-and-safe-retries)).
* The trailing `[pcs_…]` is the process-log id. Include it in any support request.
> **Shape gotcha:** on the *create* endpoints (`POST /invoices/income`, `/egress`, `/payment`, `/draft/{id}/stamp`) this object arrives nested under `message` — `{"message": {"error": …, "code": …}}`. On `DELETE /invoices/{id}` (cancel) it arrives at the top level. Parse defensively: `body.message?.code ?? body.code`.
Everything else uses the standard envelope:
```json theme={null}
{
"success": false,
"error": { "code": "invalid_request_body", "message": "…", "details": ["…"] },
"timestamp": 1718451000000
}
```
### 412 — SAT not connected
```json theme={null}
{
"message": {
"error": "Your SAT connection is not finished yet. Go to https://app.gigstack.pro/integrations and complete the SAT connection (Invoicing section) before issuing invoices.",
"code": "SAT_NOT_CONNECTED",
"retryable": false
}
}
```
The team has no usable invoicing provider — either the SAT connection was never completed, or the CSD failed validation (`code: "CSD_VALIDATION_ERROR"`). This is a **precondition on the team, not a problem with your payload**; the request never reached the PAC.
**Do:** stop retrying. Upload/refresh the CSD via `POST /v2/teams/{id}/sat-connection`, or send the team owner to the integrations page. Under Connect, check that `?team=` points at a team that has actually finished onboarding — this is the usual cause when one connected team works and another doesn't.
### 503 — PAC unavailable or outcome unknown
A `503` from a create endpoint carries one of two codes. They mean different things, so read `code`.
**`PAC_UNAVAILABLE`**
```json theme={null}
{
"message": {
"error": "El servicio de timbrado no está disponible en este momento. Vuelve a intentarlo en unos minutos. …",
"code": "PAC_UNAVAILABLE",
"retryable": true
}
}
```
The stamping provider couldn't be reached, or refused the request before stamping (for example HTTP 429, maintenance). **The document never reached the SAT**, so nothing in your data is wrong and no CFDI exists.
**Do:** retry with exponential backoff (a few minutes is usually enough). Keep the same `idempotency_key`. Don't show a validation error to your end user; nothing they entered is at fault.
**`PAC_OUTCOME_UNKNOWN`**
```json theme={null}
{
"message": {
"error": "No se recibió la respuesta del servicio de timbrado; el comprobante podría estar timbrado. …",
"code": "PAC_OUTCOME_UNKNOWN",
"retryable": true
}
}
```
The request reached the PAC and its answer was lost (a timeout, a dropped connection, an HTTP 5xx, an unreadable response). **The CFDI may exist.** Its folio is never reused for another invoice. Timeouts and 5xx answers from the PAC used to be reported as `PAC_UNAVAILABLE`; they are now this code.
* **With an `idempotency_key`** (`retryable: true`): retry with the **same** key. gigstack resends the exact same XML with the same folio, so the PAC either stamps it now or returns the stamp it already made. It can't stamp twice, and the retry isn't charged again. If the PAC can't tell, the retry answers `409` `STAMP_NEEDS_REVIEW`.
* **Without one** (`retryable: false`): a new request would take a new folio and could issue a second CFDI. Check whether the invoice exists (in `GET /invoices/income`, or in the SAT) before sending it again. Sending an `idempotency_key` on every create avoids this.
### 409 — Stamp needs review
```json theme={null}
{
"message": {
"error": "El PAC indica que el comprobante A-1284 ya fue timbrado pero no devolvió su UUID: … [pcs_…]",
"code": "STAMP_NEEDS_REVIEW",
"retryable": false
}
}
```
An earlier attempt with this `idempotency_key` reached the PAC, and gigstack could not prove whether it was stamped: the PAC said it already stamped the document but returned no UUID, the SAT's 72-hour window for the document had passed, or the invoice was stamped but couldn't be saved. Every later request with the key gets this answer, so the invoice can't be issued twice.
**Do:** contact support with the `idempotency_key` and the `[pcs_…]` id. Don't send the invoice again under a new key.
### 429 — Team credit limit reached
```json theme={null}
{
"message": "Team credit limit reached",
"error": "Team credit limit reached (500/500)",
"credit_limit": 500,
"used_credits": 500
}
```
Not a rate limit. The team has a configured `creditLimit` on issued documents and has consumed it. The counter increments **before** stamping, so this is checked and rejected up front — no folio is consumed.
Note the shape: `credit_limit` and `used_credits` are returned at the top level so you can display the exact quota. `POST /v2/receipts` reports the same condition in the standard envelope with `error.code = "team_credit_limit_reached"`.
**Do:** stop and raise the limit (`credit_limit` on `PUT /v2/teams/{id}`) or contact gigstack. Backing off does not help — the counter only moves when the limit is raised.
### 422 — Imported invoice cannot be cancelled
```json theme={null}
{
"message": "Imported invoices stamped by an external PAC cannot be canceled through gigstack. Please cancel through your original PAC provider."
}
```
Returned by `DELETE /invoices/{id}` when the invoice arrived via import (`source: "import"`) and carries no gigstack stamp. gigstack holds no credentials for the PAC that issued it, so it cannot cancel it on your behalf.
**Do:** cancel it in the system that issued it. There is no request you can change to make this succeed. `POST /invoices/sat/{uuid}/retry-xml` also uses `422` for a failed XML fetch from SAT, with `data.resource_status: "error"` — that one is worth retrying later.
### 502 — Upstream service error
```json theme={null}
{
"success": false,
"message": "PDF generator error: …"
}
```
An internal downstream service failed — most commonly the CFDI-to-PDF renderer behind `POST /invoices/sat/{uuid}/pdf`. Your invoice data is fine and, for PDF generation, the CFDI itself is untouched.
**Do:** retry with backoff. If it persists, the XML is still available through `GET /invoices/{id}/files`.
### 409 — Concurrent resource creation
```json theme={null}
{
"success": false,
"error": {
"code": "resource_conflict",
"message": "Client creation in progress for tax_id=… . Please retry."
}
}
```
Two requests tried to auto-create the same client or service (via `client.search.auto_create` / item `search.auto_create`) at the same time. One won; yours was told to wait rather than create a duplicate.
**Do:** retry once after a short delay — the resource will exist by then. `POST /v2/clients` also returns `409` for a different reason: a `search` that matched **more than one** client. That one is not retryable; narrow the search or pass the client `id`.
### 409 — Idempotency key in progress
```json theme={null}
{
"message": "An invoice with this idempotency_key is already being created",
"error": {
"code": "idempotency_in_progress",
"message": "An invoice with this idempotency_key is already being created; retry later"
},
"retryable": true
}
```
Another `POST /invoices/income` with the same `idempotency_key` is being processed right now, for example a retry sent while the first request was still waiting for the PAC. Nothing was charged or stamped by this request.
**Do:** wait a few seconds and retry with the same key. Once the first request finishes you get its outcome, for example the `400` duplicate with the invoice's `uuid`. If the first request died mid-way, the key stays held for up to 10 minutes.
### Missing Required Fields (400)
```json theme={null}
{
"message": "Invalid body",
"errors": ["client: is required"]
}
```
### Cancellation Error
Note the flat shape here — `DELETE /invoices/{id}` does **not** nest the CFDI error under `message`:
```json theme={null}
{
"error": "Error al timbrar la factura: …",
"code": "CANCEL_ERROR",
"providerMessage": "…",
"retryable": false
}
```
The SAT refused the cancellation — typically because the invoice has dependent documents (a payment complement referencing it), or the 72-hour window with acceptance rules applies. Read `providerMessage`; it names the reason.
***
For additional help with invoice management, refer to the [support documentation](https://docs.gigstack.io) or contact [support@gigstack.io](mailto:support@gigstack.io).
# Guía de Migración: API v1 a v2 de gigstack
Source: https://docs.gigstack.io/guides/migration-v1-to-v2
Integration guide for Guía de Migración: API v1 a v2 de gigstack
## 📋 Tabla de Contenidos
* [Resumen de Cambios](#-resumen-de-cambios)
* [Cambios en la URL Base](#-cambios-en-la-url-base)
* [Cambios en Autenticación](#-cambios-en-autenticación)
* [Cambios en Estructura de Respuestas](#-cambios-en-estructura-de-respuestas)
* [Cambios en Endpoints](#-cambios-en-endpoints)
* [Nuevas Funcionalidades en v2](#-nuevas-funcionalidades-en-v2)
* [Pasos para Migrar](#-pasos-para-migrar)
* [Ejemplos de Migración](#-ejemplos-de-migración)
* [Deprecación de v1](#-deprecación-de-v1)
* [Soporte y Recursos Adicionales](#-soporte-y-recursos-adicionales)
***
## 🚀 Resumen de Cambios
La API v2 de gigstack representa una mejora significativa sobre v1, con mejor estructura, nuevas funcionalidades y mayor compatibilidad con CFDI 4.0. Los principales cambios incluyen:
* ✅ Nueva URL base más consistente
* ✅ Estructura de respuestas estandarizada
* ✅ Soporte completo para CFDI 4.0
* ✅ Paginación mejorada con cursores
* ✅ gigstack Connect para multi-tenant
* ✅ Validación EFOS integrada
* ✅ Mejor manejo de errores
* ✅ Webhooks más robustos
***
## 🔗 Cambios en la URL Base
### v1 (Antigua)
```
https://gigstack-cfdi-bjekv7t4.uc.gateway.dev/v1
```
### v2 (Nueva)
```
Production: https://api.gigstack.io/v2
Pruebas: https://api.gigstack.io/v2 (mismo host; usa tu API key de test)
```
### ⚠️ Acción Requerida
Actualiza todas las referencias a la URL base en tu código:
**Antes:**
```javascript theme={null}
const baseURL = 'https://gigstack-cfdi-bjekv7t4.uc.gateway.dev/v1';
```
**Después:**
```javascript theme={null}
const baseURL = 'https://api.gigstack.io/v2';
```
***
## 🔐 Cambios en Autenticación
### v1
La autenticación en v1 utilizaba API Keys en formatos variados.
### v2
La autenticación ahora utiliza **JWT tokens** exclusivamente con el header `Authorization`.
**Formato requerido:**
```http theme={null}
Authorization: Bearer YOUR_JWT_TOKEN
```
### 📝 Cómo Obtener tu Token
1. Ingresa a [app.gigstack.pro/settings?tab=api](https://app.gigstack.pro/settings?tab=api)
2. Haz clic en "Generar nuevas llaves"
3. Usa la API Key de **test** para staging y **live** para producción
### Ejemplo de Actualización
**v1:**
```javascript theme={null}
fetch('https://gigstack-cfdi-bjekv7t4.uc.gateway.dev/v1/clients', {
headers: {
'X-API-Key': 'tu_api_key_antigua'
}
});
```
**v2:**
```javascript theme={null}
fetch('https://api.gigstack.io/v2/clients', {
headers: {
'Authorization': 'Bearer tu_jwt_token'
}
});
```
***
## 📦 Cambios en Estructura de Respuestas
Una de las mejoras más importantes en v2 es la **estandarización de respuestas**.
### v1 - Respuestas Inconsistentes
```json theme={null}
// Algunas respuestas devolvían el objeto directamente
{
"id": "client_123",
"name": "Juan Pérez"
}
// Otras envolvían en diferentes estructuras
{
"success": true,
"result": { ... }
}
```
### v2 - Respuestas Estandarizadas
La mayoría de los endpoints responden con el mismo sobre:
#### ✅ Respuesta Exitosa
```json theme={null}
{
"success": true,
"data": {
"id": "client_1234567890",
"name": "ESCUELA KEMPER URGATE",
"tax_id": "EKU9003173C9"
},
"message": "Client retrieved successfully",
"timestamp": 1767225600000
}
```
`timestamp` está en milisegundos; `message` solo aparece cuando el endpoint lo define.
#### 📋 Respuesta de Lista (Paginada)
```json theme={null}
{
"success": true,
"message": "Clients retrieved successfully",
"data": [
{ "id": "client_1", "name": "Cliente 1" },
{ "id": "client_2", "name": "Cliente 2" }
],
"next": "client_2",
"has_more": true,
"total_results": 150,
"timestamp": 1767225600000
}
```
#### ❌ Respuesta de Error
```json theme={null}
{
"success": false,
"error": {
"code": "validation_failed",
"message": "Request validation failed",
"details": ["currency: Field is required"]
},
"timestamp": 1767225600000
}
```
Algunos endpoints conservan formatos propios (por ejemplo, los errores de autenticación y la creación de facturas). Cada operación de la [referencia de la API](https://docs.gigstack.io) documenta su respuesta exacta.
### ⚠️ Acción Requerida
Actualiza tu código para acceder a la propiedad `data`:
**Antes (v1):**
```javascript theme={null}
const response = await fetch('/clients/123');
const client = await response.json();
console.log(client.name); // Acceso directo
```
**Después (v2):**
```javascript theme={null}
const response = await fetch('/clients/123');
const result = await response.json();
console.log(result.data.name); // Acceso a través de .data
```
***
## 🔀 Cambios en Endpoints
### Recursos Principales
| Recurso | v1 Endpoint | v2 Endpoint | Cambios |
| - | - | - | - |
| **Clientes** | `/clients` | `/clients` | ✅ Sin cambios en ruta |
| **Servicios** | `/products` | `/services` | ⚠️ Renombrado |
| **Facturas** | `/invoices` | `/invoices` | ✅ Mejorado con CFDI 4.0 |
| **Pagos** | `/payments` | `/payments` | ✅ Refactorizado |
| **Equipos** | ❌ No disponible | `/teams` | 🆕 Nuevo |
| **Usuarios** | ❌ No disponible | `/users` | 🆕 Nuevo |
### ⚠️ Cambio Importante: Products → Services
En v2, el recurso `products` se renombró a `services` para reflejar mejor el contexto mexicano de facturación.
**v1:**
```http theme={null}
GET /v1/products
POST /v1/products
```
**v2:**
```http theme={null}
GET /v2/services
POST /v2/services
```
***
## 🆕 Nuevas Funcionalidades en v2
### 1. gigstack Connect (Multi-tenant)
Ahora puedes acceder a recursos de **múltiples equipos** dentro de la misma cuenta de facturación agregando el parámetro `team`:
```http theme={null}
GET /v2/clients?team=team_abc123
```
**Requisitos:**
* Tu API key debe pertenecer a un equipo con gigstack Connect habilitado
* El equipo objetivo debe compartir la misma cuenta de facturación
### 2. Validación EFOS Automática
v2 incluye validación automática de clientes contra la lista de EFOS del SAT:
```json theme={null}
{
"message": "Client created successfully",
"data": {
"id": "client_1234567890",
"tax_id": "EKU9003173C9",
"efos": { "is_valid": true }
}
}
```
### 3. Paginación con Cursores
v2 utiliza paginación basada en cursores en lugar de offset/limit:
**v1 (Paginación Offset):**
```http theme={null}
GET /v1/clients?limit=20&offset=40
```
**v2 (Paginación con Cursor):**
```http theme={null}
GET /v2/clients?limit=20
GET /v2/clients?limit=20&next=client_abc123xyz
```
**Ventajas:**
* Más eficiente para conjuntos grandes de datos
* Previene resultados duplicados durante actualizaciones
* Mejor rendimiento
### 4. Soporte Completo CFDI 4.0
v2 está completamente actualizado para CFDI 4.0 con:
* Nuevos catálogos del SAT
* Complementos actualizados
* Validaciones mejoradas
* Timbrado más rápido
### 5. Webhooks
Consulta la [guía de Webhooks](/guides/webhooks) para el formato de las entregas y la verificación de firmas.
***
## 🛠️ Pasos para Migrar
### Paso 1: Preparación
1. **Revisa tu código actual** y haz una lista de todos los endpoints que usas
2. **Genera nuevos tokens JWT** desde el panel de configuración
3. **Lee la documentación completa** de v2: [docs.gigstack.io](https://docs.gigstack.io)
### Paso 2: Configuración del Entorno
1. **Crea un ambiente de prueba** usando tu API key de test
2. **Actualiza la URL base** a `https://api.gigstack.io/v2`
3. **Actualiza los headers de autenticación** a JWT Bearer tokens
### Paso 3: Actualiza tu Código
#### 3.1 Actualiza la Autenticación
```javascript theme={null}
// Configura tu cliente HTTP
const apiClient = axios.create({
baseURL: 'https://api.gigstack.io/v2',
headers: {
'Authorization': `Bearer ${process.env.GIGSTACK_JWT_TOKEN}`,
'Content-Type': 'application/json'
}
});
```
#### 3.2 Actualiza el Manejo de Respuestas
```javascript theme={null}
// Función helper para extraer data
const getData = (response) => response.data.data;
// Uso
const clients = await apiClient.get('/clients').then(getData);
```
#### 3.3 Actualiza Endpoints Renombrados
```javascript theme={null}
// Buscar y reemplazar en todo tu código
// Antes: /products
// Después: /services
// Ejemplo con sed (Linux/Mac)
// sed -i 's/\/products/\/services/g' *.js
```
#### 3.4 Actualiza Paginación
```javascript theme={null}
// v1 - Offset
async function getAllClientsV1() {
let offset = 0;
const limit = 100;
let allClients = [];
while (true) {
const response = await fetch(`/clients?limit=${limit}&offset=${offset}`);
const clients = await response.json();
if (clients.length === 0) break;
allClients.push(...clients);
offset += limit;
}
return allClients;
}
// v2 - Cursor
async function getAllClientsV2() {
let cursor = null;
let allClients = [];
while (true) {
const url = cursor
? `/clients?limit=100&next=${cursor}`
: '/clients?limit=100';
const response = await fetch(url);
const result = await response.json();
allClients.push(...result.data);
if (!result.has_more) break;
cursor = result.next;
}
return allClients;
}
```
### Paso 4: Prueba Exhaustivamente
1. **Prueba todos los flujos críticos** en staging
2. **Verifica el manejo de errores** con casos edge
3. **Compara resultados** entre v1 y v2 si es posible
4. **Documenta cualquier discrepancia**
### Paso 5: Deploy Gradual
1. **Despliega primero en staging**
2. **Monitorea logs y errores** por 24-48 horas
3. **Realiza deploy a producción** en horario de baja actividad
4. **Mantén rollback disponible** por si surgen problemas
***
## 💡 Ejemplos de Migración
### Ejemplo 1: Crear Cliente
**v1:**
```javascript theme={null}
const response = await fetch('https://gigstack-cfdi-bjekv7t4.uc.gateway.dev/v1/clients', {
method: 'POST',
headers: {
'X-API-Key': 'antigua_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Juan Pérez',
rfc: 'XAXX010101000',
email: 'juan@example.com'
})
});
const client = await response.json();
console.log(client.id); // Acceso directo
```
**v2:**
```javascript theme={null}
const response = await fetch('https://api.gigstack.io/v2/clients', {
method: 'POST',
headers: {
'Authorization': 'Bearer tu_jwt_token',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'ESCUELA KEMPER URGATE',
tax_id: 'EKU9003173C9',
tax_system: '601',
email: 'contabilidad@ejemplo.com'
})
});
const result = await response.json();
console.log(result.data.id); // Acceso a través de .data
console.log(result.data.efos.is_valid); // Nueva funcionalidad
```
### Ejemplo 2: Listar Facturas con Paginación
**v1:**
```javascript theme={null}
const response = await fetch(
'https://gigstack-cfdi-bjekv7t4.uc.gateway.dev/v1/invoices?limit=50&offset=0',
{
headers: { 'X-API-Key': 'antigua_api_key' }
}
);
const invoices = await response.json();
invoices.forEach(inv => console.log(inv.uuid));
```
**v2:**
```javascript theme={null}
const response = await fetch(
'https://api.gigstack.io/v2/invoices/income?limit=50',
{
headers: { 'Authorization': 'Bearer tu_jwt_token' }
}
);
const result = await response.json();
result.data.forEach(inv => console.log(inv.uuid));
// Si hay más páginas
if (result.has_more) {
const nextPage = await fetch(
`https://api.gigstack.io/v2/invoices/income?limit=50&next=${result.next}`,
{ headers: { 'Authorization': 'Bearer tu_jwt_token' } }
);
}
```
### Ejemplo 3: Crear Servicio (antes Producto)
**v1:**
```javascript theme={null}
const response = await fetch('https://gigstack-cfdi-bjekv7t4.uc.gateway.dev/v1/products', {
method: 'POST',
headers: {
'X-API-Key': 'antigua_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Consultoría',
price: 1000,
sat_code: '84111506'
})
});
const product = await response.json();
```
**v2:**
```javascript theme={null}
const response = await fetch('https://api.gigstack.io/v2/services', {
method: 'POST',
headers: {
'Authorization': 'Bearer tu_jwt_token',
'Content-Type': 'application/json'
},
body: JSON.stringify({
description: 'Consultoría',
unit_price: 1000,
product_key: '84111506',
unit_key: 'E48'
})
});
const result = await response.json();
const service = result.data;
```
### Ejemplo 4: gigstack Connect (Multi-team)
**Nueva funcionalidad en v2:**
```javascript theme={null}
// Acceder a clientes de otro equipo
const response = await fetch(
'https://api.gigstack.io/v2/clients?team=team_abc123',
{
headers: { 'Authorization': 'Bearer tu_jwt_token' }
}
);
const result = await response.json();
console.log(`Clientes del equipo: ${result.data.length}`);
```
***
## ⏰ Deprecación de v1
### Línea de Tiempo
* **Hoy:** v2 está disponible para todos los usuarios
* **\[Fecha TBD]:** v1 entra en modo de mantenimiento (solo corrección de bugs críticos)
* **\[Fecha TBD]:** v1 será descontinuada completamente
### Recomendaciones
⚠️ **Migra lo antes posible** - v1 no recibirá nuevas funcionalidades y eventualmente será descontinuada.
✅ **Planifica tu migración** - Usa esta guía para crear un plan de migración estructurado.
📧 **Mantente informado** - Suscríbete a las actualizaciones en [app.gigstack.pro/settings](https://app.gigstack.pro/settings?subtab=notifications).
***
## 📞 Soporte y Recursos Adicionales
### Documentación
* **Documentación completa v2:** [docs.gigstack.io](https://docs.gigstack.io)
* **Ejemplos de código:** Disponibles en múltiples lenguajes en la documentación
### Colecciones y Herramientas
* **Colección de Postman v2:** Solicita acceso a través de soporte
* **Webhooks:** [Guía de Webhooks](/guides/webhooks)
* **SDK oficial:** Próximamente disponible
### Soporte
* **Email:** [soporte@gigstack.io](mailto:soporte@gigstack.io)
* **Chat en vivo:** Disponible en [app.gigstack.pro](https://app.gigstack.pro)
* **Documentación v1 (referencia):** [Ver documentación antigua en Postman](https://documenter.getpostman.com/view/21022234/UyxnFQzk)
***
## ✅ Checklist de Migración
Usa esta lista para asegurar una migración exitosa:
### Preparación
* [ ] Revisé toda la documentación de v2
* [ ] Generé nuevos JWT tokens (test y producción)
* [ ] Identifiqué todos los endpoints que uso actualmente
* [ ] Creé un ambiente de staging para pruebas
### Código
* [ ] Actualicé la URL base a `https://api.gigstack.io/v2`
* [ ] Cambié autenticación a `Authorization: Bearer`
* [ ] Actualicé manejo de respuestas para acceder a `.data`
* [ ] Renombré todos los endpoints `/products` a `/services`
* [ ] Implementé nueva paginación con cursores
* [ ] Agregué manejo de errores estandarizado
### Pruebas
* [ ] Probé crear, leer, actualizar y eliminar recursos
* [ ] Verifiqué paginación con conjuntos grandes de datos
* [ ] Validé manejo de errores y casos edge
* [ ] Confirmé que webhooks funcionan correctamente
* [ ] Comparé resultados entre v1 y v2
### Deploy
* [ ] Desplegué a staging y monitoré por 48 horas
* [ ] Documenté cualquier issue encontrado
* [ ] Realicé deploy a producción
* [ ] Configuré monitoreo de logs y errores
* [ ] Tengo plan de rollback preparado
***
## 🎉 ¡Migración Exitosa!
Una vez completada la migración, disfrutarás de:
* ⚡ **Mejor rendimiento** con paginación optimizada
* 🛡️ **Mayor confiabilidad** con estructura estandarizada
* 🆕 **Nuevas funcionalidades** como gigstack Connect y validación EFOS
* 📊 **Mejor observabilidad** con respuestas más claras
* 🚀 **Preparado para el futuro** con CFDI 4.0 completo
***
## 📚 Referencia: Documentación v1
Si necesitas consultar la documentación antigua de v1 para referencia durante tu migración:
**[Ver Documentación v1 en Postman →](https://documenter.getpostman.com/view/21022234/UyxnFQzk)**
*Nota: Esta documentación se mantiene solo como referencia. Te recomendamos migrar a v2 lo antes posible.*
***
**¿Necesitas ayuda con tu migración?** Contáctanos en [soporte@gigstack.io](mailto:soporte@gigstack.io) o abre un ticket en [app.gigstack.pro/support](https://app.gigstack.pro/support)
**Última actualización:** Septiembre 2026
# Payments API Guide
Source: https://docs.gigstack.io/guides/payments
Integration guide for Payments
Some operations in this guide have Discovery handlers but no public gateway route yet: `POST /payments/{id}/support-documents`. Check the availability notice on each API reference page before using them.
Process, track, and manage payments with automatic invoice generation and Mexican tax compliance. The Payments API handles payment registration, processing, refunds, and automated CFDI creation.
## Overview
The Payments API provides comprehensive payment management with flexible automation options. Register payments manually, create payment requests, process refunds, and automatically generate compliant invoices.
## Key Features
* **Payment Registration** - Record payments with optional invoice automation
* **Payment Requests** - Create shareable payment links
* **Client Search** - Find and update existing clients to prevent duplicates
* **Payment Splitting** - Split payments between master and connect teams (marketplace)
* **Refund Processing** - Full or partial refund support
* **Invoice Automation** - Automatic PUE/PPD invoice creation
* **PPD Complement Linking** - Link payments to existing PPD invoices for automatic complement generation
* **Multiple Processors** - Support for various payment gateways
* **Status Tracking** - Real-time payment status updates
* **Idempotency** - Prevent duplicate payment processing
## Endpoints
### List Payments
```http theme={null}
GET /payments
```
Retrieve a paginated list of payments with powerful filtering capabilities.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `next` | string | Pagination cursor for next page |
| `team` | string | gigstack Connect: Target team ID |
| `status` | string | Filter by payment status (`requires_payment_method`, `succeeded`, `canceled`) |
| `currency` | string | Filter by currency code (e.g., `MXN`, `USD`) |
| `client_id` | string | Filter by client ID |
| `email` | string | Filter by client email address |
| `tax_id` | string | Filter by client tax ID (RFC) |
| `client_name` | string | Filter by client name |
| `metadata.{key}` | string | Filter by metadata field (e.g., `metadata.order_id=ORD-123`) |
| `created` | object | Filter by creation date (e.g., `created={gte:1700000000000}`) |
| `order_by` | string | Field to sort by (`timestamp`, `amount`) |
| `sort` | string | Sort direction (`asc`, `desc`) |
**Metadata Filtering:**
You can filter payments by any metadata key using dot notation or underscore notation:
```bash theme={null}
# Dot notation
GET /payments?metadata.order_id=ORD-12345
# Underscore notation (alternative)
GET /payments?metadata_order_id=ORD-12345
```
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/payments?limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example with Filters:**
```bash theme={null}
# Filter by client email
curl -X GET "https://api.gigstack.io/v2/payments?email=cliente@ejemplo.com" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by tax ID (RFC)
curl -X GET "https://api.gigstack.io/v2/payments?tax_id=XAXX010101000" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by metadata
curl -X GET "https://api.gigstack.io/v2/payments?metadata.order_id=ORD-12345" \
-H "Authorization: Bearer YOUR_TOKEN"
# Multiple filters
curl -X GET "https://api.gigstack.io/v2/payments?status=succeeded¤cy=MXN&metadata.project_id=PROJ-001" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Payments retrieved successfully",
"data": [
{
"id": "payment_1234567890",
"client": {
"id": "client_1234567890",
"name": "Juan Pérez García",
"tax_id": "PEGJ800101ABC"
},
"status": "succeeded",
"total": 1160.0,
"subtotal": 1000.0,
"taxes": 160.0,
"currency": "MXN",
"payment_form": "03",
"invoices": ["invoice_1234567890"],
"created_at": 1677651234,
"succeeded_at": 1677651234,
"payment_processor": "stripe",
"short_url": "https://gigstack.xyz/Xk3mP9"
}
],
"has_more": false,
"total_results": 1
}
```
### Search Payments
```http theme={null}
GET /payments/search
```
Full-text search across payments using Typesense. This endpoint provides fast, typo-tolerant search capabilities.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `query` | string (required) | Search query (searches across client name, email, payment ID, description, metadata) |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `page` | integer | Page number for pagination (default: 1) |
| `fields` | string | Comma-separated list of fields to search in |
| `status` | string | Filter by payment status |
| `currency` | string | Filter by currency code |
| `client_id` | string | Filter by client ID |
**Example Request:**
```bash theme={null}
# Basic search
curl -X GET "https://api.gigstack.io/v2/payments/search?query=juan" \
-H "Authorization: Bearer YOUR_TOKEN"
# Search with filters
curl -X GET "https://api.gigstack.io/v2/payments/search?query=consulting&status=succeeded¤cy=MXN" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Payments searched successfully",
"data": [
{
"id": "payment_1234567890",
"client": {
"id": "client_1234567890",
"name": "Juan Pérez García"
},
"status": "succeeded",
"total": 1160.0
}
],
"found": 15,
"page": 1,
"per_page": 10,
"success": true
}
```
### Register Payment
```http theme={null}
POST /payments/register
```
Register a payment that has already been received. This endpoint marks the payment as 'succeeded' immediately and is used for recording payments that have already been completed through external means (bank transfers, cash, etc.).
**Key Features:**
* Payment is marked as 'succeeded' immediately
* Requires `payment_form` field (Mexican SAT payment form code)
* Optional invoice automation
* Client search to prevent duplicate client records
* Used for payments already received
**Automation Types:**
* `pue_invoice` - Create PUE (Pago en Una sola Exhibición) invoice immediately
* `none` - No automation, register payment only
**Request Body:**
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"exchange_rate": 1.0,
"payment_form": "03",
"items": [
{
"id": "service_1234567890",
"quantity": 2,
"unit_price": 1000.0
}
],
"idempotency_key": "payment-register-12345",
"date": 1677651234000,
"metadata": {
"order_id": "ORD-12345",
"customer_reference": "REF-789"
}
}
```
**Optional Fields:**
* `idempotency_key` (string) - Unique key to prevent duplicate payment registrations. If a payment with this key already exists, the existing payment will be returned.
* `date` (number) - Unix timestamp (in milliseconds) for when the payment was received. Must be in the past. Defaults to current time if not provided.
* `exchange_rate` (number) - Exchange rate for currency conversion. If not provided, the rate from the payment date (or current date if no date specified) will be fetched automatically from our rates collection.
* `ppd_invoice_id` (string) - UUID of an existing PPD invoice to link this payment to. When provided, a payment complement (complemento de pago) will be automatically generated and linked to the PPD invoice. The referenced invoice must have `payment_method='PPD'`, `status='valid'` and `invoice_type='I'` — a payment complement only ever settles an income CFDI, never an egress one.
**Payment Form Codes:**
* `01` - Cash
* `02` - Check
* `03` - Electronic transfer
* `04` - Credit card
* `05` - Electronic money
* `06` - Digital money
* `99` - To be defined
**Client Search Feature:**
The `search` parameter enables upsert-like behavior to find existing clients before creating payments, helping to avoid duplicate client records:
* `on_key` (string, required) - Field to search on (e.g., 'tax\_id', 'email', 'name')
* `on_value` (string, required) - Value to match against the specified field
* `update` (boolean, optional) - If true and a match is found, update the existing client with the provided data. If false, return the existing client without modifications. Default: false
**Search Behavior:**
* If a single client matches the search criteria, that client is used for the payment
* If `update: true`, the matched client is updated with any new data provided in the request
* If multiple clients match the search criteria, a 409 Conflict error is returned to prevent ambiguity
* If no client matches and client data is provided, a new client is created automatically
**Example with Client Search:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": false
},
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [
{
"description": "Professional services",
"quantity": 1,
"unit_price": 5000.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
]
}'
```
### Request Payment
```http theme={null}
POST /payments/request
```
Create a payment request that generates a shareable payment link. This endpoint creates a payment in 'requires\_payment\_method' status and provides customers with various payment options to complete the transaction.
**Key Features:**
* Payment is created with 'requires\_payment\_method' status
* Generates shareable payment link
* Supports multiple payment methods
* Optional email notifications
* Used for requesting future payments
* Optionally returns the payer to your site after paying (`success_url`)
**Payment Methods:**
* `card` - Credit/debit card payments *(requires Stripe integration)*
* `spei` - Mexican bank transfer (SPEI)
* `oxxo` - OXXO convenience store payments *(requires Stripe integration)*
* `stripe-spei` - Customer balance payments *(requires Stripe integration)*
**Note:** `card`, `oxxo`, and `stripe-spei` payment methods are only available when your team has Stripe connected.
**Request Body:**
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"items": [
{
"id": "service_1234567890",
"quantity": 1
}
],
"automation_type": "pue_invoice",
"allowed_payment_methods": ["card", "bank", "oxxo"],
"send_email": true,
"emails": ["client@example.com"],
"idempotency_key": "payment-request-12345",
"success_url": "https://tienda.com/pedido/1234/gracias",
"metadata": {
"invoice_number": "INV-2024-001"
}
}
```
### Returning the payer to your site (`success_url`)
By default the hosted payment page is where the journey ends. For a shop
checkout that is a dead end: the customer pays and is left on a page with no
order reference and no way back, which reads as a failed purchase and leads to
duplicate payments.
Set `success_url` to your order confirmation page and gigstack sends them back:
```json theme={null}
{
"success_url": "https://tienda.com/pedido/1234/gracias"
}
```
The payer sees the destination host and is redirected a few seconds after the
payment is confirmed, and can also return immediately with a button. For
asynchronous methods (SPEI, OXXO) confirmation may arrive after the payer has
closed the page, so treat your webhook — not this redirect — as the source of
truth for whether a payment succeeded.
The value is validated on write. A `400` is returned unless the URL:
* uses `https`
* carries no credentials (`https://user:pass@host`)
* contains no whitespace or control characters
* resolves to a fully qualified, publicly reachable host — loopback, private
and link-local addresses are rejected
* is at most 2048 characters
It is returned as `success_url` on payment reads, and is accepted only on
`POST /payments/request`. `/payments/register` records payments that already
settled, where there is no payer to redirect.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/request \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"currency": "MXN",
"items": [
{
"description": "Consulting services",
"quantity": 1,
"unit_price": 10000.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}
],
"automation_type": "pue_invoice",
"allowed_payment_methods": ["card", "bank"],
"send_email": true,
"emails": ["client@example.com"]
}'
```
**Response with Payment Link:**
```json theme={null}
{
"message": "Payment request created successfully",
"data": {
"id": "payment_1234567890",
"short_url": "https://gigstack.xyz/Xk3mP9",
"status": "requires_payment_method",
"total": 11600.0
}
}
```
### Get Payment
```http theme={null}
GET /payments/{id}
```
Retrieve a specific payment by ID.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/payments/payment_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Mark Payment as Paid
```http theme={null}
POST /payments/{id}/paid
```
Mark a pending payment as paid.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/payment_1234567890/paid \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Cancel Payment
```http theme={null}
DELETE /payments/{id}
```
Cancel a payment request.
**Example Request:**
```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/payments/payment_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Refund Payment
```http theme={null}
POST /payments/{id}/refund
```
Process a refund for a payment.
**Request Body (Optional):**
```json theme={null}
{
"amount": 500.0,
"reason": "Partial refund for service adjustment",
"items": [
{
"id": "service_1234567890",
"quantity": 1
}
]
}
```
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/payment_1234567890/refund \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reason": "Customer requested cancellation"
}'
```
### Support Documents
```http theme={null}
POST /payments/{id}/support-documents
GET /payments/{id}/support-documents
```
Attach and list evidence for a payment (proof of payment, payment confirmation…). Same fields, limits and response as [invoice support documents](/guides/invoices#support-documents).
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/payment_1234567890/support-documents \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@comprobante-transferencia.pdf" \
-F "documentType=payment_proof"
```
## Payment Structure
### Payment Status
| Status | Description |
| - | - |
| `requires_payment_method` | Waiting for payment method |
| `succeeded` | Payment completed successfully |
| `canceled` | Payment canceled |
### Payment Processors
* **stripe** - Stripe payment gateway
* **mercado\_pago** - MercadoPago
* **paypal** - PayPal
* **manual** - Manual/bank transfer
## Complex Payment Examples
### Register Payment with Client Search and Update
This example demonstrates how to search for an existing client by tax ID and update their information if found, or create a new client if not found:
```json theme={null}
{
"client": {
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": true
},
"name": "Juan Pérez García",
"email": "juan.perez.updated@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601",
"phone": "+52 55 1234 5678",
"address": {
"street": "Av. Reforma",
"exterior": "456",
"zip": "06600",
"city": "Ciudad de México",
"state": "CDMX"
}
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [
{
"description": "Monthly subscription",
"quantity": 1,
"unit_price": 1500.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
]
}
```
**Key Points:**
* With `update: true`, if a client with tax\_id "PEGJ800101ABC" exists, their email, phone, and address will be updated
* If no client is found, a new client will be created with all the provided information
* The search ensures you don't create duplicate clients when processing recurring payments
### Register Payment with Multiple Items
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "04",
"items": [
{
"id": "service_001",
"quantity": 2,
"unit_price": 1000.0
},
{
"description": "Installation service",
"quantity": 1,
"unit_price": 500.0,
"product_key": "72121400",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
},
{
"description": "Express shipping",
"quantity": 1,
"unit_price": 200.0,
"product_key": "78102200",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
]
}
```
### Register Payment with Withholding Taxes
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [
{
"description": "Professional services",
"quantity": 1,
"unit_price": 10000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
},
{
"type": "ISR",
"rate": 0.1,
"withholding": true
},
{
"type": "IVA",
"rate": 0.106667,
"withholding": true
}
]
}
]
}
```
### Payment Request with Custom Invoice Config
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"items": [
{
"id": "service_1234567890",
"quantity": 1
}
],
"automation_type": "pue_invoice",
"allowed_payment_methods": ["card", "bank"],
"send_email": true,
"emails": ["client@example.com"],
"invoice_config": {
"serie": "B",
"folio": "456"
},
"metadata": {
"project_id": "PROJ-2024-001",
"department": "Engineering"
}
}
```
### Register USD Payment with Exchange Rate
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"automation_type": "pue_invoice",
"currency": "USD",
"exchange_rate": 18.5,
"payment_form": "03",
"items": [
{
"description": "International consulting",
"quantity": 10,
"unit_price": 100.0,
"product_key": "80141503",
"unit_key": "HUR",
"taxes": [
{
"type": "IVA",
"rate": 0.0,
"factor": "Exento"
}
]
}
]
}
```
### Payment Splitting (Marketplace)
Split payments between a master team (platform) and a connect team (merchant) in marketplace scenarios. This is only available for master teams with marketplace-enabled billing accounts.
**Important:** When using `transfer_data`, the `team` and `livemode` fields are automatically extracted from the authentication token. Developers do not need to send these fields in the request body.
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [
{
"description": "Professional consulting services",
"quantity": 1,
"unit_price": 1000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
],
"transfer_data": {
"master": 60,
"connect": "ABC123456789",
"master_to": "client",
"connect_to": "client"
},
"idempotency_key": "marketplace-payment-12345"
}
```
**Transfer Data Configuration:**
* `master` (number, 0-100) - Percentage of the payment for the master team
* `connect` (string) - Tax ID (RFC) or Team ID of the connect team. If not found, a new team will be created
* `master_to` (string: 'client' | 'connect') - Client assignment for master payment:
* `client`: Use the original client from the request
* `connect`: Create the connect team as a client for the master payment
* `connect_to` (string: 'client' | 'master') - Client assignment for connect payment:
* `client`: Use the original client from the request
* `master`: Create the master team as a client for the connect payment
* `connect_custom_config` (object, optional) - Customize items in the connect payment:
* `product_key` (string) - SAT product key for connect payment items
* `unit_key` (string) - SAT unit key for connect payment items
* `custom_description` (string) - Custom description for connect payment items
* `custom_price` (number) - Fixed amount for connect payment (overrides percentage calculation)
* `taxes` (array) - Custom tax configuration for connect payment items
**Split Payment Response:**
```json theme={null}
{
"message": "Split payment registered successfully",
"data": {
"split_reference": "split_abc123xyz",
"master_payment_id": "payment_master_123",
"connect_payment_id": "payment_connect_456",
"master_amount": 696.0,
"connect_amount": 464.0,
"total_amount": 1160.0,
"master_payment": {
"id": "payment_master_123",
"client": "client_1234567890",
"amount": 696.0,
"team": "team_master_123",
"split_role": "master"
},
"connect_payment": {
"id": "payment_connect_456",
"client": "client_connect_789",
"amount": 464.0,
"team": "team_connect_456",
"split_role": "connect"
},
"connect_team": {
"id": "team_connect_456",
"tax_id": "EMP800101ABC",
"legal_name": "Empresa Ejemplo SA de CV",
"is_newly_created": true,
"onboarding_url": "https://embeded.gigstack.pro/?sessionId=otpOnboarding_7Kd2Pq&c=193847"
}
}
}
```
**When a new team is created:**
If the connect team doesn't exist, the response includes an `onboarding_url` that can be sent to the merchant to complete their team setup.
### Advanced: Split Payment with Custom Configuration
```json theme={null}
{
"client": {
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": false
},
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [
{
"description": "Platform service with marketplace split",
"quantity": 1,
"unit_price": 1000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
],
"transfer_data": {
"master": 30,
"connect": "EMP800101ABC",
"master_to": "client",
"connect_to": "master",
"connect_custom_config": {
"product_key": "01010101",
"unit_key": "E48",
"custom_description": "Professional consulting services",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
}
]
}
}
}
```
### Fixed Commission Fee (Custom Price)
Instead of percentage-based splitting, you can charge a fixed commission fee using `custom_price`. This is useful when you want to charge a flat platform fee regardless of the transaction amount.
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [
{
"description": "Product sale",
"quantity": 1,
"unit_price": 1000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
],
"transfer_data": {
"master": 0,
"connect": "EMP800101ABC",
"master_to": "client",
"connect_to": "master",
"connect_custom_config": {
"custom_price": 50.00,
"custom_description": "Platform service fee"
}
}
}
```
**Key Points:**
* When `custom_price` is set, it overrides the percentage calculation
* The connect team gets the exact `custom_price` amount (e.g., \$50)
* The master team gets the remainder (e.g., $1110 on a $1160 total)
* The `master` percentage field is ignored when using `custom_price`
* Useful for fixed platform fees, minimum commissions, or tiered pricing
**Response with Custom Price:**
```json theme={null}
{
"message": "Split payment registered successfully",
"data": {
"split_reference": "split_abc123xyz",
"master_payment_id": "payment_master_123",
"connect_payment_id": "payment_connect_456",
"master_amount": 1110.00,
"connect_amount": 50.00,
"total_amount": 1160.00,
"used_custom_price": true
}
}
```
## Common Scenarios
### 1. Register Completed Payment (Bank Transfer)
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [{
"id": "service_1234567890",
"quantity": 1
}],
"metadata": {
"bank_reference": "TRF-2024-0123"
}
}'
```
### 2. Create Payment Link
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/request \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"currency": "MXN",
"items": [{
"description": "Monthly subscription",
"quantity": 1,
"unit_price": 999.00,
"product_key": "81111500",
"unit_key": "MON",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"automation_type": "pue_invoice",
"allowed_payment_methods": ["card", "bank", "oxxo"],
"send_email": true,
"emails": ["client@example.com"]
}'
```
### 3. Register Cash Payment
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "01",
"items": [{
"id": "service_1234567890",
"quantity": 5
}],
"metadata": {
"cashier": "employee_001",
"receipt_number": "CASH-2024-0456"
}
}'
```
### 4. Register Payment with PPD Invoice Complement
Register a payment linked to an existing PPD invoice. This automatically generates a payment complement (complemento de pago) CFDI.
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"automation_type": "none",
"currency": "MXN",
"payment_form": "03",
"ppd_invoice_id": "invoice_ppd_1234567890",
"items": [{
"id": "service_1234567890",
"quantity": 1,
"unit_price": 5000.00
}]
}'
```
**Key Points:**
* The `ppd_invoice_id` must reference a valid PPD **income** invoice (`payment_method='PPD'`, `status='valid'`, `invoice_type='I'`)
* The payment complement is generated automatically by proserver after the payment is created
* You can use `automation_type: "none"` since the complement is handled via `ppd_invoice_id`
* The PPD invoice's `payments` array is updated to link back to this payment
### 5. Partial Refund
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/payment_1234567890/refund \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": 580.00,
"reason": "50% discount applied retroactively"
}'
```
### 6. Marketplace Payment Split (Platform Fee)
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"update": false
},
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601"
},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [{
"description": "Marketplace transaction",
"quantity": 1,
"unit_price": 1000.0,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"transfer_data": {
"master": 10,
"connect": "ABC123456789",
"master_to": "client",
"connect_to": "client"
},
"metadata": {
"marketplace_transaction_id": "TXN-2024-001",
"merchant_reference": "MERCHANT-123"
}
}'
```
Response includes split payment details with master (10%) and connect (90%) payment IDs.
### 7. Marketplace Payment with Fixed Commission
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [{
"description": "Product sale",
"quantity": 2,
"unit_price": 500.0,
"product_key": "43211500",
"unit_key": "H87",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"transfer_data": {
"master": 0,
"connect": "VENDOR123ABC",
"master_to": "connect",
"connect_to": "client",
"connect_custom_config": {
"custom_price": 25.00,
"custom_description": "Marketplace commission"
}
}
}'
```
Connect (marketplace) receives fixed $25 commission, master (vendor) receives the remainder ($1135 from \$1160 total).
## Payment Workflows
### Immediate Payment (PUE)
```mermaid theme={null}
graph LR
A[Register Payment] --> B[Create PUE Invoice]
B --> C[Stamp with SAT]
C --> D[Send to Client]
```
### Deferred Payment (PPD)
```mermaid theme={null}
graph LR
A[Register Payment] --> B[Create PPD Invoice]
B --> C[Receive Payment]
C --> D[Create Payment Complement]
D --> E[Stamp with SAT]
```
### Payment Request Flow
```mermaid theme={null}
graph LR
A[Create Request] --> B[Generate Link]
B --> C[Client Pays]
C --> D[Mark as Paid]
D --> E[Generate Invoice]
```
## Best Practices
1. **Use idempotency keys** - Prevent duplicate payments by providing a unique `idempotency_key` with each request.
2. **Use client search** - Leverage the `search` parameter to avoid duplicate client records when processing recurring payments.
3. **Set automation\_type correctly** - Choose the appropriate automation type based on your invoicing workflow (PUE for immediate, PPD for deferred).
4. **Include all items** - Ensure complete and accurate payment information for proper invoice generation.
5. **Validate clients first** - Verify fiscal data (RFC, tax system) before processing payments to avoid stamp failures.
6. **Use metadata** - Track internal references, order IDs, and other business-specific data for reconciliation.
7. **Handle webhooks** - Process payment status updates to keep your systems synchronized.
8. **Test in staging** - Validate all workflows in the staging environment before deploying to production.
## Related Resources
* [Clients API](/guides/clients) - Manage payment recipients
* [Services API](/guides/services) - Configure payment items
* [Invoices API](/guides/invoices) - Generated invoices from payments
* [Teams API](/guides/teams) - Configure payment settings
## Error Handling
Payment endpoints use the standard envelope throughout:
```json theme={null}
{
"success": false,
"error": { "code": "invalid_request_body", "message": "…", "details": "…" },
"timestamp": 1718451000000
}
```
### At a glance
| Status | When | Retry the same request? | What you should do |
| - | - | - | - |
| `400` | Body validation, invalid state transition, refund over the total, bad payment form | No | Fix the payload — see below. |
| `401` | Missing/invalid token, or no resolvable team | No | Re-authenticate. |
| `403` | The payment belongs to another team or the other livemode | No | Use the matching key or `?team=`. |
| `404` | Payment, client or service not found | No | Check the id. |
| `409` | Search matched **multiple** clients/services, **or** a concurrent auto-create is in flight | Depends — see below | See below. |
| `500` | Unhandled server error | Maybe | Report with the process id. |
> Payments themselves do not stamp CFDIs synchronously. When a payment carries an `automation_type` that produces an invoice, the stamping happens **downstream and asynchronously** — a `2xx` from `POST /payments/register` means the payment was recorded, not that a CFDI exists. Subscribe to the `invoice.created` / `invoice.failed` webhook events for the stamping outcome, and expect the CFDI-specific statuses (`412` SAT not connected, `503` PAC unavailable, `429` credit limit) to surface [on the invoice endpoints](/guides/invoices#error-handling) rather than here.
### 409 — Multiple clients match search criteria
```json theme={null}
{
"success": false,
"error": {
"code": "resource_conflict",
"message": "Multiple clients found matching tax_id=\"…\". Please use a more specific search criteria.",
"details": ["client_abc123", "client_def456"]
}
}
```
`details` lists the ids that matched, so you can resolve the ambiguity without a second query. **Not retryable** — the same request will conflict again.
**Do:** pass the exact client `id`, or search on a more selective key (`tax_id` over `name`), or deduplicate the clients.
### 409 — Concurrent resource creation
```json theme={null}
{
"success": false,
"error": {
"code": "resource_conflict",
"message": "Client creation in progress for tax_id=… . Please retry."
}
}
```
A different in-flight request is auto-creating the same client (or `Service creation in progress …` for an item). gigstack refused to race it rather than create a duplicate.
**Do:** this one **is** retryable — wait \~1s and send the same request again; the resource will exist. If you are firing many payments for a new client in parallel, create the client once up front and reference it by `id`.
### 400 — Invalid state transition
The lifecycle endpoints reject operations that do not apply to the payment's current status:
| Endpoint | Message |
| - | - |
| `POST /payments/{id}/paid` | `Payment is already marked as paid` |
| `POST /payments/{id}/paid` | `Cannot mark a cancelled payment as paid` |
| `POST /payments/{id}/paid` | `date cannot be in the future` |
| `DELETE /payments/{id}` | `Cannot cancel a payment that has already succeeded` |
| `DELETE /payments/{id}` | `Payment is already cancelled` |
| `POST /payments/{id}/refund` | `Can only refund payments that have succeeded` |
All terminal. Read the payment first (`GET /payments/{id}`) and branch on `status` rather than probing.
### 400 — Refund exceeds the payment
```json theme={null}
{
"success": false,
"error": {
"code": "invalid_request_body",
"message": "Total refunded amount (1500 pesos) cannot exceed payment amount (1000 pesos)"
}
}
```
The check is against the **cumulative** total, not this one refund — prior partial refunds count. Compute the remaining refundable amount from `amount - amountRefunded` on the payment before requesting.
### 400 — External processor refund not allowed
```json theme={null}
{
"success": false,
"error": {
"code": "invalid_request_body",
"message": "External processor refund is not allowed for this payment"
}
}
```
You set `external_processor_refund: true` on a payment that has no `paymentIntent` — there is no upstream charge for gigstack to refund. Manually registered payments (cash, transfer) are in this category.
**Do:** omit `external_processor_refund` to record the refund in gigstack only, and move the money back yourself.
### Failed Client Fiscal Information (400)
When a client's fiscal information fails validation (invalid RFC or EFOS blacklist), the payment is rejected. Update the client (`PUT /v2/clients/{id}`) and retry.
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Platform Payouts API Guide
Source: https://docs.gigstack.io/guides/platform-payouts
Integration guide for Platform Payouts
## Overview
Platform payouts is for marketplaces and digital platforms that pay **providers** under the SAT digital-platforms scheme (Plataformas Tecnológicas: RESICO régimen `625`, retention key `26`). Ride-hailing drivers, delivery couriers, lodging hosts and sellers of goods are all providers. The platform issues the CFDIs for a whole payout period from two files:
* a **movements** file, with one row per payout to a provider;
* a **commissions** file, with one row per provider per month holding the platform commission.
gigstack matches every row to the provider's gigstack team in your billing account and computes a plan. Once you confirm the plan, it stamps every document in the background.
> **What it is not.** This API is for a platform that invoices **on behalf of its providers**. If your company invoices its own sales, one CFDI per sale, use [`POST /invoices/income`](/guides/invoices) instead.
Base path: `https://api.gigstack.io/v2/platform-payouts`
A run goes through three steps:
1. **Plan.** `POST /platform-payouts` uploads both files. The plan is computed during the request and returned in the response. Nothing is stamped yet.
2. **Review.** `GET /platform-payouts/{id}` returns the totals. `GET /platform-payouts/{id}/movements` lists every row with what will be issued for it, or why nothing will be.
3. **Confirm.** `POST /platform-payouts/{id}/confirm` hands the plan to the stamping worker. **This cannot be undone.** Poll `GET /platform-payouts/{id}` until the run is `completed` or `failed`, then read its [`result`](#run-result) to see how it went.
## Key Features
* **Three documents per payout period** - the provider's income invoice, your retention certificate and your monthly commission invoice
* **Tax policy per account** - the regimes, SAT keys and withholding rates come from your account's configuration (see [Tax Policy](#tax-policy))
* **Synchronous planning** - the plan, or the reason it failed, is in the response to the upload
* **Idempotent uploads** - a required `Idempotency-Key` makes a retried upload return the same run. If the key comes back with different files, it is refused
* **Row-level exclusions** - a malformed or ineligible row is excluded with a reason and a stable code, and the other rows still go ahead
* **A clear outcome** - a finished run reports `result`: `completed`, `partially_completed` or `failed`
* **No double certification** - a provider-month already certified by an earlier run is skipped
* **Safe confirm** - confirming twice returns the run's status and does nothing else
## Who Can Use It
Platform payouts is for **marketplace master teams**. All of these must hold:
| Requirement | Error when it doesn't (`403`) |
| - | - |
| The team is a marketplace master team | `not_master_team` |
| The team has a billing account | `no_billing_account` |
| Platform payouts is enabled on that billing account (ask support to enable it) | `feature_disabled` |
On `POST /platform-payouts`, these checks (and `team_not_found` / `not_a_member`) run right after the `Idempotency-Key` check and **before the upload is read**. The files of a refused request are never processed or stored.
Credentials:
* **API keys and OAuth access tokens** belong to the team. They can create, read and confirm that team's runs.
* **User-scoped tokens** (MCP tokens, dashboard sessions) act as a person, who must be a member of the team (`403 forbidden` / `not_a_member` otherwise). The two `POST` routes also require **`editor` permission on invoices** (`403 forbidden`, `This action requires editor access to invoices`).
* **gigstack Connect doesn't apply.** Runs always act on the credential's own master team. Sending `?team=` moves the request to a team that isn't a master team. Uploads and confirms are then refused with `not_master_team`, and reads return `404`.
**The mode comes from the credential only.** A test key creates and sees test runs, and a live key creates and sees live runs. A run of another team, or of the other mode, returns `404 run_not_found`, never `403`, so an id can't be used to discover someone else's run.
## What Gets Issued
For each movement (payout) and each provider-month, the plan decides which of three comprobantes to issue:
| Kind (`planned_documents`) | Issued by | Issued to | One per | Notes |
| - | - | - | - | - |
| `income` | The provider's gigstack team | Your master team | Movement | CFDI de ingreso for the payout, with the ISR and IVA withholdings |
| `certificate` | Your master team | The provider | Movement | Constancia de Retenciones, key `26` (Plataformas Tecnológicas). Reports this movement's share of the monthly commission |
| `commission` | Your master team | The provider | Provider-month in the commissions file | Invoice for the month's platform commission |
The monthly commission is **prorated** across the provider's movements of that month, in proportion to each subtotal. That share is the movement's `commission`, and it is what the certificate reports. The commission invoice bills the full monthly figure.
Which documents a provider qualifies for depends on your account's [tax policy](#tax-policy). Rows that don't qualify are excluded with a Spanish reason and a stable code. See [Exclusion codes](#exclusion-codes).
## Tax Policy
The documents a run issues, and the SAT keys and rates they carry, are set **per master account** when the account is onboarded. The same files can produce different CFDIs for a delivery platform and for a ride-hailing platform. The policy covers:
* **Eligibility**: which tax regimes providers may be on, whether a provider needs a valid CSD (sellos) in gigstack, and whether a certificate may be issued to *público en general* (the generic RFC).
* **Retention certificate**: the service type (`tipoDeServ`) and subtype (`subTipServ`) of the digital-platforms complement, and the ISR and IVA withholding rates.
* **Invoices**: the product keys of the income and commission invoices, the unit, the concept descriptions, and the CFDI use, payment form, payment method and currency.
If your account has no specific configuration, the defaults apply. The defaults are set for **ground passenger transport**, so check them before your first live run:
| Setting | Default |
| - | - |
| Allowed tax regimes | `625` (RESICO) |
| CSD required | Yes |
| Certificates to *público en general* | Not allowed |
| Retention key (`CveRetenc`) | `26` |
| Service type / subtype (`tipoDeServ` / `subTipServ`) | `01` (ground passenger transport) / `04` |
| Periodicity | `02` (monthly) |
| ISR withholding | 2.1% |
| IVA withholding | 8% (on 16% IVA) |
| Income invoice product key | `78101800` |
| Commission invoice product key | `80141600` |
| Unit | `E48` (Unidad de servicio) |
| Income concept | `Servicio de transporte terrestre de pasajeros - {movementType} - {date}`, filled from `Tipo de movimiento` and `Fecha del movimiento` |
| Commission concept | `Comision por uso de plataforma tecnologica - {month}`, filled from `Mes` |
| CFDI use, payment form, method, currency | `G03`, `03`, `PUE`, `MXN` |
If your providers deliver goods, host lodging or provide another kind of service, **contact support to configure your policy** before your first live run. The policy can't be changed through the API.
## Input Files
Both files are sent in one `multipart/form-data` request:
| Form field | Content |
| - | - |
| `movements_file` | One row per payout |
| `commissions_file` | One row per provider per month |
Rules for both:
* **The format is chosen by extension** (the MIME type is ignored). `.csv` and `.txt` are read as comma-separated text (UTF-8, a BOM is fine). `.xlsx`, `.xls` and `.xlsm` are read as workbooks, and only the **first sheet** is used.
* **Max 5 MB** per file (`413 file_too_large`) and **max 50,000 rows** (the plan fails).
* The first row is the header. Headers match regardless of case, accents and repeated spaces (`CORREO ELECTRONICO` matches `Correo electrónico`). Extra columns are ignored.
* **Amounts** are in MXN. `1250.00`, `1,250.00`, `1250,5` and `$1,250.00` are all read. Any other value is a row problem, never a silent zero.
* **Column names**: each column below has a primary name and a few accepted alternatives. Use the primary name in new files. When a column is missing, the error names it by its primary name.
### Provider columns (both files)
| Column | Also accepted | Required | Format |
| - | - | - | - |
| `ID del proveedor` | `Provider ID`, `Driver ID` | Yes | Your id for the provider. Matched against the `metadata.driverId` of the provider's team |
| `Nombre del proveedor` | `Nombre del conductor`, `Razón social`, `Nombre` | Yes | The provider's legal name. In the movements file, it must match the legal name of the provider's gigstack team, or the income invoice is excluded |
| `Correo electrónico` | `Correo`, `Email` | Yes | Used to match the provider when the id and `RFC` don't |
| `RFC` | | Yes | The provider's RFC |
When a file has more than one name column, the more specific one wins: `Nombre del proveedor` or `Nombre del conductor`, then `Razón social`, then a bare `Nombre`.
### Movements file columns
The [provider columns](#provider-columns-both-files), plus:
| Column | Required | Format |
| - | - | - |
| `Fecha del movimiento` | Yes | `YYYY-MM-DD` or `DD/MM/YYYY` (`-` is also accepted as the separator) |
| `Tipo de movimiento` | Yes | Free text used in the invoice concept, for example `Pago semanal` or `Servicio` |
| `Subtotal` | Yes | At least `0.01` |
### Commissions file columns
The [provider columns](#provider-columns-both-files), plus:
| Column | Also accepted | Required | Format |
| - | - | - | - |
| `Mes` | | Yes | `YYYY-MM` |
| `Comisión` | `Comisión Total`, and a few legacy export spellings | Yes | The month's platform commission, MXN |
Commissions are joined to movements on the provider id and the month. The commission invoice is excluded if the row has no `RFC`. If a provider-month appears more than once, the first row is used.
**A certificate needs a commission.** The SAT rejects a retention certificate whose commission is zero (`SPT147`). A movement whose provider-month has no commission in the commissions file therefore gets no certificate.
### File problems and row problems
* A **file** problem fails the whole plan. The run comes back as `plan_failed` with a Spanish `error`. Examples: the file can't be read, it's empty, it has more than 50,000 rows, or required columns are missing (all missing columns are listed at once, for example `Al archivo de comisiones le faltan estas columnas: Comisión.`).
* A **row** problem (an invalid date or amount, a subtotal below `0.01`) only excludes that movement, with the problem as its reason.
### Matching providers
Each row is matched to a team **in your billing account**. gigstack tries the provider id first (the team's `metadata.driverId`), then the `RFC`, then the e-mail. When an RFC or e-mail matches more than one team, the row is excluded rather than guessed.
## Run Status
| `status` | Meaning | What to do |
| - | - | - |
| `planning` | The plan is being computed | Poll `GET /platform-payouts/{id}`. You normally only see this on a retry of an upload that is still in progress |
| `plan_ready` | Plan computed, waiting for confirmation | Review it, then confirm |
| `plan_failed` | The plan couldn't be computed; see `error` | Fix the file and upload again with a **new** `Idempotency-Key` |
| `stamping` | Confirmed; the worker is issuing the CFDIs | Poll `GET /platform-payouts/{id}` |
| `completed` | The worker has nothing left to try | Read `result`: this status says nothing about how much was issued |
| `failed` | The worker stopped the whole run; see `error` | Contact support. Causes: the master team no longer exists or has no CSD, or the run made no progress after repeated attempts |
## Run Result
`status: "completed"` only means the worker has nothing left to try. To know how a finished run went, **read `result`, not `status`**. It is `null` until the run finishes (`completed` or `failed`).
| `result` | When |
| - | - |
| `completed` | No document failed: everything planned was issued |
| `partially_completed` | Some documents failed, and at least one document was issued |
| `failed` | The run itself failed (`status: "failed"`), or it finished having issued nothing while something failed |
How it's counted:
* **Failures** are `progress.failed_count` plus `progress.commission_failed_count`. A movement counts as failed as soon as **one** of its documents (income invoice or certificate) failed. Commission invoices are counted separately, because `failed_count` counts movements only.
* **Issued** documents are `income_invoices_count` + `certificates_count` + `commission_invoices_count`.
Runs that finished before `result` existed get it derived from their counters by the same rule.
## Endpoints
### Create a Run
```http theme={null}
POST /platform-payouts
```
Uploads both files and returns the planned run.
**Headers:**
| Header | Required | Description |
| - | - | - |
| `Authorization` | Yes | `Bearer YOUR_TOKEN` |
| `Idempotency-Key` | Yes | 8-128 characters from `A-Z a-z 0-9 . _ : -`. Checked before the files are read |
**Form fields:** `movements_file` and `commissions_file`, both required. See [Input Files](#input-files).
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/platform-payouts \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Idempotency-Key: payouts-2026-08-v1" \
-F "movements_file=@movimientos-agosto-2026.csv" \
-F "commissions_file=@comisiones-agosto-2026.xlsx"
```
**Response (201):**
```json theme={null}
{
"success": true,
"data": {
"id": "batchrun_3f9a1c7e2b4d6f8a0c1e3a5b7d9f1a2c",
"status": "plan_ready",
"result": null,
"livemode": true,
"team": "team_1234567890",
"created_at": 1788220800000,
"plan_ready_at": 1788220804120,
"confirmed_at": null,
"completed_at": null,
"files": {
"movements": "movimientos-agosto-2026.csv",
"commissions": "comisiones-agosto-2026.xlsx"
},
"months": ["2026-08"],
"total_movements": 412,
"included_count": 398,
"excluded_count": 14,
"planned_documents": { "income": 398, "certificate": 398, "commission": 57 },
"exclusion_summary": {
"Sin sellos (CSD) en Gigstack": 9,
"El proveedor no tiene cuenta en Gigstack": 5
},
"exclusion_code_summary": {
"missing_csd": 9,
"provider_not_found": 5
},
"progress": {
"stamped_count": 0,
"failed_count": 0,
"income_invoices_count": 0,
"certificates_count": 0,
"commission_invoices_count": 0,
"commission_failed_count": 0,
"income_invoices_amount": 0
},
"error": null
},
"timestamp": 1788220804180
}
```
A plan that failed on the file is also a `201`, with `status: "plan_failed"`, zero counts and the reason in `error`:
```json theme={null}
{
"success": true,
"data": {
"id": "batchrun_9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e",
"status": "plan_failed",
"error": "Al archivo de movimientos le faltan estas columnas: Fecha del movimiento, Subtotal.",
"...": "..."
},
"timestamp": 1788220801070
}
```
#### Idempotency
The run id is derived from your team, the credential's mode and the `Idempotency-Key`, and the run records a fingerprint of the two files' **contents** (their bytes; the file names don't count). So:
* The **first** request with a key creates the run: `201`.
* A later request with the **same key and the same files** returns that run, in whatever status it is now: `200`. Nothing is planned again.
* The **same key with different files** is refused: `409 idempotency_key_reused`. The new files aren't planned; send them under a new key. Renaming a file doesn't change the fingerprint, and changing a single byte does.
* Runs created before the fingerprint existed have none. A later request with their key is answered as before, `200` with the existing run, whatever files it carries.
* A retry that arrives while the first request is still planning gets the run in `planning`. Poll `GET /platform-payouts/{id}`; don't upload again.
* A plan that failed **stays failed under its key**. Fix the file and upload with a **new** key.
* The same key used with a test key and with a live key names two different runs.
If a run is stuck in `planning` for more than 10 minutes (the instance planning it died), a retry with the same key takes it over and plans it again from the files in that retry.
Use one key per distinct upload, for example `payouts-2026-08-v1`, then `payouts-2026-08-v2` after a fix.
### Get a Run
```http theme={null}
GET /platform-payouts/{id}
```
Returns the run in the same shape as the create response. After confirming, `progress` fills in as the worker stamps, and `result` is set once the run finishes.
```bash theme={null}
curl https://api.gigstack.io/v2/platform-payouts/batchrun_3f9a1c7e2b4d6f8a0c1e3a5b7d9f1a2c \
-H "Authorization: Bearer YOUR_TOKEN"
```
A finished run in which a few documents failed:
```json theme={null}
{
"success": true,
"data": {
"id": "batchrun_3f9a1c7e2b4d6f8a0c1e3a5b7d9f1a2c",
"status": "completed",
"result": "partially_completed",
"livemode": true,
"team": "team_1234567890",
"created_at": 1788220800000,
"plan_ready_at": 1788220804120,
"confirmed_at": 1788221400000,
"completed_at": 1788224100000,
"files": {
"movements": "movimientos-agosto-2026.csv",
"commissions": "comisiones-agosto-2026.xlsx"
},
"months": ["2026-08"],
"total_movements": 412,
"included_count": 398,
"excluded_count": 14,
"planned_documents": { "income": 398, "certificate": 398, "commission": 57 },
"exclusion_summary": {
"Sin sellos (CSD) en Gigstack": 9,
"El proveedor no tiene cuenta en Gigstack": 5
},
"exclusion_code_summary": {
"missing_csd": 9,
"provider_not_found": 5
},
"progress": {
"stamped_count": 395,
"failed_count": 3,
"income_invoices_count": 397,
"certificates_count": 396,
"commission_invoices_count": 56,
"commission_failed_count": 1,
"income_invoices_amount": 525528.75
},
"error": null
},
"timestamp": 1788224160000
}
```
### List Movements
```http theme={null}
GET /platform-payouts/{id}/movements
```
One entry per row of the movements file, in file order, with the state of its income invoice and retention certificate.
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer | Movements per page, 1-100 (default **50**). Anything else is `400 invalid_limit` |
| `next` | string | Opaque cursor from the previous page's `data.next`. An invalid one is `400 invalid_cursor` |
Note the nested shape: the array is at `data.data` and the cursor at `data.next`. Keep requesting with `next` while `data.has_more` is `true`. The cursor stays valid while the worker updates statuses, so pages never overlap or skip rows.
```bash theme={null}
curl "https://api.gigstack.io/v2/platform-payouts/batchrun_3f9a1c7e2b4d6f8a0c1e3a5b7d9f1a2c/movements?limit=3" \
-H "Authorization: Bearer YOUR_TOKEN"
```
```json theme={null}
{
"success": true,
"data": {
"data": [
{
"id": "P10482_2026-08-04_125000_0",
"line": 2,
"status": "stamped",
"provider": {
"id": "P10482",
"name": "ESCUELA KEMPER URGATE",
"email": "proveedor@example.com",
"tax_id": "EKU9003173C9",
"team": "team_0987654321"
},
"movement_type": "Pago semanal",
"date": "2026-08-04",
"month": "2026-08",
"subtotal": 1250,
"commission": 96.15,
"exclusion_reason": null,
"exclusion_codes": [],
"error": null,
"income": {
"planned": true,
"status": "stamped",
"reason": null,
"reason_code": null,
"invoice_id": "7C2E4B1A-9D3F-4E8A-B6C5-1F2A3B4C5D6E",
"uuid": "7C2E4B1A-9D3F-4E8A-B6C5-1F2A3B4C5D6E",
"total": 1323.75,
"error": null,
"error_code": null
},
"certificate": {
"planned": true,
"status": "stamped",
"reason": null,
"reason_code": null,
"invoice_id": "0A1B2C3D-4E5F-4A6B-8C7D-9E0F1A2B3C4D",
"uuid": "0A1B2C3D-4E5F-4A6B-8C7D-9E0F1A2B3C4D",
"total": null,
"error": null,
"error_code": null
}
},
{
"id": "P20931_2026-08-04_98000_0",
"line": 3,
"status": "excluded",
"provider": {
"id": "P20931",
"name": "ESCUELA KEMPER URGATE",
"email": "otro.proveedor@example.com",
"tax_id": "EKU9003173C9",
"team": "team_1122334455"
},
"movement_type": "Pago semanal",
"date": "2026-08-04",
"month": "2026-08",
"subtotal": 980,
"commission": 75.38,
"exclusion_reason": "Sin sellos (CSD) en Gigstack",
"exclusion_codes": ["missing_csd"],
"error": null,
"income": {
"planned": false,
"status": "skipped",
"reason": "Sin sellos (CSD) en Gigstack",
"reason_code": "missing_csd",
"invoice_id": null,
"uuid": null,
"total": null,
"error": null,
"error_code": null
},
"certificate": {
"planned": false,
"status": "skipped",
"reason": "Sin sellos (CSD) en Gigstack",
"reason_code": "missing_csd",
"invoice_id": null,
"uuid": null,
"total": null,
"error": null,
"error_code": null
}
},
{
"id": "P30577_2026-08-05_110000_0",
"line": 4,
"status": "failed",
"provider": {
"id": "P30577",
"name": "ESCUELA KEMPER URGATE",
"email": "tercer.proveedor@example.com",
"tax_id": "EKU9003173C9",
"team": "team_5566778899"
},
"movement_type": "Pago semanal",
"date": "2026-08-05",
"month": "2026-08",
"subtotal": 1100,
"commission": 84.62,
"exclusion_reason": null,
"exclusion_codes": [],
"error": "Timbrado interrumpido: verificar en el PAC antes de reintentar",
"income": {
"planned": false,
"status": "skipped",
"reason": "Sin serie de facturación configurada",
"reason_code": "missing_series",
"invoice_id": null,
"uuid": null,
"total": null,
"error": null,
"error_code": null
},
"certificate": {
"planned": true,
"status": "failed",
"reason": null,
"reason_code": null,
"invoice_id": null,
"uuid": null,
"total": null,
"error": "Timbrado interrumpido: verificar en el PAC antes de reintentar",
"error_code": "interrupted_stamp"
}
}
],
"next": "4",
"has_more": true
},
"timestamp": 1788224160000
}
```
The third movement had only its certificate planned (the provider's team has no invoice series, so the income invoice is skipped with `reason_code: "missing_series"`). A movement is `excluded` only when **neither** document is planned; then `exclusion_codes` lists the distinct codes of both.
Commission invoices are not listed per provider-month; the run only reports how many were planned (`planned_documents.commission`), stamped (`progress.commission_invoices_count`) and failed (`progress.commission_failed_count`).
### Confirm a Run
```http theme={null}
POST /platform-payouts/{id}/confirm
```
> **Irreversible.** Confirming hands the plan to the stamping worker. A stamped CFDI can only be cancelled, not undone. Review the plan first.
No request body. Answers as soon as the run is handed over; the stamping itself happens in the background (the worker runs every minute).
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/platform-payouts/batchrun_3f9a1c7e2b4d6f8a0c1e3a5b7d9f1a2c/confirm \
-H "Authorization: Bearer YOUR_TOKEN"
```
```json theme={null}
{
"success": true,
"data": {
"id": "batchrun_3f9a1c7e2b4d6f8a0c1e3a5b7d9f1a2c",
"status": "stamping"
},
"timestamp": 1788221400000
}
```
* **Safe to retry.** Confirming a run that is already `stamping` or `completed` returns `200` with its current status and does nothing else.
* Only a `plan_ready` run with at least one planned document can be confirmed. Otherwise: `409 plan_not_ready` (still planning), `409 not_confirmable` (`plan_failed` or `failed`), `409 nothing_to_stamp` (every document was excluded).
* The entitlement is checked again: if platform payouts was disabled on the billing account since the upload, the confirm is refused with `403 feature_disabled`.
#### Provider-months already certified
A provider's monthly commission is prorated over the movements of **this** file. If an earlier run already certified the same provider and month, certifying it again would report the platform commission twice to the SAT. So, when you confirm, the run reserves every provider-month it certifies:
* A provider-month held by an **earlier confirmed run** of your team (same mode) is given up: those movements' `certificate` becomes `skipped`, with `reason_code: "certificate_month_reserved"` and a reason naming the earlier run (`La corrida batchrun_… ya reservó las constancias de 2026-08 para este proveedor`).
* The run's `included_count`, `excluded_count`, `exclusion_summary`, `exclusion_code_summary` and `planned_documents.certificate` are recomputed to match. A movement left with nothing to issue becomes `excluded`, with `exclusion_codes: ["certificate_month_reserved"]`.
* Income and commission invoices are not affected.
Provider-months that an earlier run had already reserved when this one was **planned** are excluded at planning time instead, with the same code and the reason `La corrida … ya emitió constancias de … para este proveedor: vuelve a subir el mes completo o emítelas por separado`.
To certify a month correctly, upload **all** of the provider's movements for that month in one run.
#### Following progress
Poll `GET /platform-payouts/{id}` every 30-60 seconds. The worker stamps one document at a time, with a short pause between stamps, so a run of several hundred movements takes several minutes or more.
* `progress.stamped_count` / `progress.failed_count` count **movements**.
* `progress.income_invoices_count`, `certificates_count` and `commission_invoices_count` count **comprobantes**; `commission_failed_count` counts failed commission invoices. `income_invoices_amount` is the total of the stamped income invoices in MXN.
* When the run finishes, read [`result`](#run-result). If it is `partially_completed` or `failed`, list the movements and look for `status: "failed"`, the document's `error` (Spanish, for people) and its `error_code` (for programs).
* A transient failure (for example the PAC being unavailable, `error_code` `NETWORK_ERROR` or `HTTP_503`) is retried, up to 3 attempts per document; while it waits, the document stays `planned` with the last `error` and `error_code`. A SAT rejection is final; its `error_code` is the CFDI error code (for example `STAMPING_ERROR`).
* A stamp that was sent to the PAC but never recorded fails with `error_code: "interrupted_stamp"` and `Timbrado interrumpido: verificar en el PAC antes de reintentar`. It is not retried automatically, because retrying could issue a second CFDI for the same payout. Contact support.
* If a provider's team was moved out of your billing account, or scheduled for deletion, after the plan was made, its documents fail with `error_code: "team_out_of_scope"` and nothing is issued for it (`La cuenta del proveedor ya no pertenece a esta cuenta de facturación: no se emitió nada.` or `La cuenta del proveedor está programada para eliminarse: no se emitió nada.`). If the team was deleted, the `error` is `La cuenta del proveedor ya no existe en Gigstack.`
**Finding the stamped documents.** Each document's `uuid` is the SAT folio fiscal. For a `certificate`, `invoice_id` is the retention's id, readable with [`GET /retentions/{id}`](/guides/retentions#get-retention). For `income`, `invoice_id` is an invoice of the **provider's** team (`provider.team`), not of yours; read it with the [`team` parameter](/guides/gigstack-connect) if your plan includes gigstack Connect.
## End-to-End Example
```bash theme={null}
TOKEN="YOUR_TOKEN"
BASE="https://api.gigstack.io/v2/platform-payouts"
# 1. Upload and plan. Keep the key: retrying with it is safe.
RUN_ID=$(curl -s -X POST "$BASE" \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: payouts-2026-08-v1" \
-F "movements_file=@movimientos-agosto-2026.csv" \
-F "commissions_file=@comisiones-agosto-2026.xlsx" | jq -r '.data.id')
# 2. Wait for the plan (only needed if the upload was retried while planning).
while true; do
RUN=$(curl -s "$BASE/$RUN_ID" -H "Authorization: Bearer $TOKEN")
STATUS=$(echo "$RUN" | jq -r '.data.status')
[ "$STATUS" != "planning" ] && break
sleep 5
done
if [ "$STATUS" = "plan_failed" ]; then
echo "$RUN" | jq -r '.data.error' # fix the file, upload again with a new key
exit 1
fi
# 3. Review the plan: totals, then every excluded movement.
echo "$RUN" | jq '.data | {total_movements, included_count, planned_documents, exclusion_code_summary}'
NEXT=""
while true; do
PAGE=$(curl -s "$BASE/$RUN_ID/movements?limit=100${NEXT:+&next=$NEXT}" -H "Authorization: Bearer $TOKEN")
echo "$PAGE" | jq -r '.data.data[] | select(.status == "excluded") | "\(.line)\t\(.provider.id)\t\(.exclusion_codes | join(","))\t\(.exclusion_reason)"'
[ "$(echo "$PAGE" | jq -r '.data.has_more')" = "true" ] || break
NEXT=$(echo "$PAGE" | jq -r '.data.next')
done
# 4. Confirm (irreversible). Retrying the confirm is safe.
curl -s -X POST "$BASE/$RUN_ID/confirm" -H "Authorization: Bearer $TOKEN" | jq '.data'
# 5. Poll until the worker finishes.
while true; do
RUN=$(curl -s "$BASE/$RUN_ID" -H "Authorization: Bearer $TOKEN")
STATUS=$(echo "$RUN" | jq -r '.data.status')
echo "$RUN" | jq -c '.data.progress'
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 60
done
echo "$RUN" | jq '.data | {status, result, error, progress}'
# result: completed, partially_completed (list the failed movements) or failed
```
## Response Objects
### Run
| Field | Type | Description |
| - | - | - |
| `id` | string | `batchrun_…`. Derived from team, mode and `Idempotency-Key` |
| `status` | string | See [Run Status](#run-status) |
| `result` | string \| null | `completed`, `partially_completed` or `failed` once the run finishes, `null` before. See [Run Result](#run-result) |
| `livemode` | boolean | Mode of the credential that created the run |
| `team` | string | Your master team |
| `created_at` | integer | Epoch ms |
| `plan_ready_at` | integer \| null | When planning ended (ready or failed) |
| `confirmed_at` | integer \| null | When the run was confirmed |
| `completed_at` | integer \| null | When the run became `completed` or `failed` |
| `files.movements`, `files.commissions` | string | Uploaded file names |
| `months` | string\[] | `YYYY-MM` months in the movements file, ascending |
| `total_movements` | integer | Rows in the movements file |
| `included_count` | integer | Movements with at least one document to issue |
| `excluded_count` | integer | Movements with nothing to issue |
| `planned_documents` | object | `income`, `certificate`, `commission`: CFDIs the plan issues |
| `exclusion_summary` | object | Excluded movements counted by reason, for people. Keys are Spanish reason text that may be reworded |
| `exclusion_code_summary` | object | Excluded movements counted by [exclusion code](#exclusion-codes), for programs. Only codes that occur are present. A movement with two codes counts under each, so the values can add up to more than `excluded_count` |
| `progress` | object | `stamped_count`, `failed_count` (movements); `income_invoices_count`, `certificates_count`, `commission_invoices_count` (documents stamped); `commission_failed_count` (commission invoices failed); `income_invoices_amount` (MXN) |
| `error` | string \| null | Spanish reason for `plan_failed` / `failed` |
### Movement
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique within the run. Treat as opaque |
| `line` | integer | Row in the movements file (header is line 1) |
| `status` | string | `planned`, `excluded`, `stamped` (all planned documents issued), `failed` (at least one failed) |
| `provider` | object | `id` (`ID del proveedor`), `name` and `tax_id` (from the matched team, else from the file), `email`, `team` (matched team id or `null`) |
| `movement_type` | string | `Tipo de movimiento` |
| `date` | string | `YYYY-MM-DD`; empty if the file's date was invalid |
| `month` | string | `YYYY-MM` |
| `subtotal` | number | MXN, rounded to the cent for display |
| `commission` | number | Prorated share of the monthly commission, MXN |
| `exclusion_reason` | string \| null | Spanish reason when `status` is `excluded` (two reasons are joined with `·`) |
| `exclusion_codes` | string\[] | The distinct [exclusion codes](#exclusion-codes) behind `exclusion_reason`. Empty when the movement isn't excluded |
| `error` | string \| null | Last stamping error, Spanish |
| `income`, `certificate` | object | Document state, below |
### Document state (`income`, `certificate`)
| Field | Type | Description |
| - | - | - |
| `planned` | boolean | Whether the plan issues it |
| `status` | string | `planned`, `skipped`, `stamped`, `failed` |
| `reason` | string \| null | Why it isn't planned, in Spanish |
| `reason_code` | string \| null | Why it isn't planned, as an [exclusion code](#exclusion-codes). `null` when planned |
| `invoice_id` | string \| null | Stamped document id (see [Following progress](#following-progress)) |
| `uuid` | string \| null | SAT folio fiscal |
| `total` | number \| null | Income invoice total, MXN. `null` for certificates |
| `error` | string \| null | Last stamping error, in Spanish |
| `error_code` | string \| null | The stamping or PAC error code behind `error`: `interrupted_stamp`, `NETWORK_ERROR`, `HTTP_` (e.g. `HTTP_503`), `team_out_of_scope`, or a CFDI error code (e.g. `STAMPING_ERROR`, `PAC_UNAVAILABLE`; see [CFDI Errors](/guides/catalogs/cfdi_errors)). `null` when there is no error |
### Exclusion codes
Every reason a document isn't planned comes with a **stable code**: `reason_code` on the document, `exclusion_codes` on an excluded movement, and the keys of the run's `exclusion_code_summary`. **Branch on the codes.** Codes are never renamed (new ones may be added); the Spanish sentences in `reason`, `exclusion_reason` and `exclusion_summary` are for people and may be reworded at any time.
| Code | Documents | Meaning | Spanish reason (today) |
| - | - | - | - |
| `invalid_row` | All | The row itself is malformed (bad date, amount or month) | The row problem, e.g. `Fecha del movimiento inválida (…)`, `Subtotal inválido (…)`, `El subtotal debe ser de al menos 0.01` |
| `provider_not_found` | Income, certificate | No gigstack team of your billing account matches the provider | `El proveedor no tiene cuenta en Gigstack` |
| `missing_tax_id` | Income, commission | No RFC: neither on the team nor in the file (commission: none in the commissions file) | `Sin RFC`, `Sin RFC en el archivo` |
| `missing_legal_name` | Income, certificate | The provider's team has no legal name (certificate: neither the team nor the file has one) | `Sin razón social en Gigstack`, `Sin razón social` |
| `missing_zip` | Income, certificate | The provider's team has no fiscal zip code | `Sin código postal fiscal` |
| `missing_fiscal_data` | Commission | No provider team, or it lacks legal name or fiscal zip code | `Sin datos fiscales en Gigstack` |
| `csd_expired` | All | The provider's CSD (sellos) in gigstack expired | `Sellos (CSD) vencidos en Gigstack` |
| `missing_csd` | All | The provider's team has no CSD in gigstack | `Sin sellos (CSD) en Gigstack` |
| `tax_system_not_allowed` | All | The provider's tax regime isn't allowed by your [tax policy](#tax-policy) | `Régimen …, se pidió emitir solo a 625` |
| `duplicate_tax_id` | Income, certificate | The RFC matches more than one team of your billing account | `El RFC está en más de una cuenta: …` |
| `name_mismatch` | Income | `Nombre del proveedor` differs from the team's legal name (SAT registry check) | `El nombre del archivo no coincide con el de la cuenta ("…")` |
| `missing_series` | Income, commission | The issuing team has no invoice series: the provider's team for income, your master team for commissions | `Sin serie de facturación configurada` |
| `public_general_not_allowed` | Certificate | The certificate would need the generic RFC and your tax policy forbids issuing to the general public | `Requeriría RFC genérico y se pidió no emitir al público en general` |
| `certificate_month_reserved` | Certificate | An earlier run already certified this provider-month (at planning or at confirm, see [Provider-months already certified](#provider-months-already-certified)) | `La corrida … ya emitió constancias de …` / `La corrida … ya reservó las constancias de …` |
| `missing_commission` | Certificate | No commission for the month, so the SAT would reject the certificate (`SPT147`) | `Falta el archivo de comisiones de 2026-08: …` |
| `zero_commission` | Commission | The month's commission in the commissions file is zero | `La comisión del mes es cero` |
## Error Handling
All errors use the standardized envelope. Messages are in Spanish (a few validation messages are in English); **branch on `error.code`**, not on the message.
```json theme={null}
{
"success": false,
"error": {
"code": "plan_not_ready",
"message": "El plan todavía se está calculando."
},
"timestamp": 1788221400000
}
```
Authentication failures (`401`, and the `403` for a revoked key or a plan without API access) come from the authentication layer with a raw `{ "message": … }` body; see [Authentication errors](/guides/welcome#authentication-errors).
| Status | `error.code` | Endpoints | When |
| - | - | - | - |
| `400` | `invalid_request_body` | `POST /platform-payouts` | `Idempotency-Key` missing or malformed |
| `400` | `invalid_content_type` | `POST /platform-payouts` | Body isn't `multipart/form-data` |
| `400` | `file_required` | `POST /platform-payouts` | `movements_file` or `commissions_file` missing (`details` names it) |
| `400` | `empty_file` | `POST /platform-payouts` | A file is zero bytes |
| `400` | `invalid_file_format` | `POST /platform-payouts` | Extension isn't `.csv`, `.txt`, `.xlsx`, `.xls`, `.xlsm` |
| `400` | `unexpected_file` | `POST /platform-payouts` | An extra file field, a field sent twice, or more than two files |
| `400` | `too_many_fields` | `POST /platform-payouts` | More than 10 plain form fields |
| `400` | `file_upload_error` | `POST /platform-payouts` | The multipart body couldn't be parsed |
| `400` | `invalid_limit` | `GET /{id}/movements` | `limit` not an integer 1-100 |
| `400` | `invalid_cursor` | `GET /{id}/movements` | `next` isn't a valid cursor |
| `401` | `unauthorized` | All | Missing or invalid credential |
| `403` | `not_master_team` | `POST /platform-payouts`, `POST /{id}/confirm` | Team isn't a marketplace master team (also with Connect's `team`) |
| `403` | `no_billing_account` | `POST /platform-payouts`, `POST /{id}/confirm` | Team has no billing account |
| `403` | `feature_disabled` | `POST /platform-payouts`, `POST /{id}/confirm` | Platform payouts isn't enabled on the billing account |
| `403` | `team_not_found` | `POST /platform-payouts`, `POST /{id}/confirm` | The team document doesn't exist |
| `403` | `not_a_member` | `POST /platform-payouts`, `POST /{id}/confirm` | User-scoped token, user isn't a team member |
| `403` | `forbidden` | `POST /platform-payouts`, `POST /{id}/confirm` | User-scoped token: not a member, or no `editor` permission on invoices |
| `404` | `run_not_found` | `GET /{id}`, `GET /{id}/movements`, `POST /{id}/confirm` | No such run in your team and mode (includes malformed ids) |
| `404` | `resource_not_found` | `GET /{id}`, `GET /{id}/movements`, `POST /{id}/confirm` | User-scoped token whose user isn't a member of the team |
| `409` | `idempotency_key_reused` | `POST /platform-payouts` | The `Idempotency-Key` was already used with different file contents. Use a new key for new files |
| `409` | `run_exists` | `POST /platform-payouts` | The derived run id is held by another team's run. Not expected in practice; use another key |
| `409` | `plan_not_ready` | `POST /{id}/confirm` | Run is still `planning` |
| `409` | `not_confirmable` | `POST /{id}/confirm` | Run is `plan_failed` or `failed` |
| `409` | `nothing_to_stamp` | `POST /{id}/confirm` | The plan issues no documents |
| `413` | `file_too_large` | `POST /platform-payouts` | A file exceeds 5 MB |
| `500` | `internal_server_error` | All | Unexpected failure. Retrying (with the same `Idempotency-Key` for uploads) is safe |
A problem **inside** a file isn't an HTTP error: the upload answers `201` with `status: "plan_failed"` and the reason in `data.error`.
## Best Practices
1. **Use one `Idempotency-Key` per upload** and reuse it only to retry that same upload (same file contents). After fixing a file, use a new key; reusing the old one is `409 idempotency_key_reused`.
2. **Review before confirming.** Check `exclusion_code_summary` and list the excluded movements; fix provider data in gigstack or the files and upload again rather than confirming a plan with surprises.
3. **Upload whole months.** Certificates prorate the monthly commission over the movements in the file. A provider-month split across runs is only certified by the first.
4. **Include the commissions for every month** in the movements file, or those movements get no certificate.
5. **Try it with a test key first.** Test runs are separate from live runs.
6. **Poll gently.** Every 30-60 seconds is enough; the worker runs once a minute.
7. **Read `result`, not `status`**, when the run finishes. On `partially_completed` or `failed`, list the failed movements and branch on each document's `error_code`.
8. **Branch on codes, not sentences**: `error.code`, `reason_code`, `exclusion_codes`, `error_code`.
## Related Resources
* [Retentions API](/guides/retentions) - Read the retention certificates a run issued
* [Invoices API](/guides/invoices) - Income and commission invoices
* [gigstack Connect](/guides/gigstack-connect) - Read documents of the providers' teams
* [CFDI Errors Reference](/guides/catalogs/cfdi_errors) - Stamping errors you may see in a document's `error`
* [Test Mode](/guides/welcome#test-mode)
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Receipts API Guide
Source: https://docs.gigstack.io/guides/receipts
Integration guide for Receipts
Create, manage, and stamp receipts that can be converted into CFDI invoices. The Receipts API handles pre-invoice document creation with flexible validity periods and SAT compliance for later stamping.
## Overview
The Receipts API provides comprehensive receipt management for pre-invoice documents. Create receipts with automatic calculations, flexible validity periods, and stamp them as CFDI invoices when needed.
## Key Features
* **Receipt Creation** - Create pre-invoice receipts with automatic calculations
* **Flexible Validity** - Set custom validity periods (day, week, month, etc.)
* **CFDI Stamping** - Convert receipts to compliant CFDI invoices
* **Client Integration** - Full client management and auto-creation support
* **Enhanced Metadata Support** - Track custom data, references, and business identifiers with full flexibility
* **Periodicity Control** - Manage receipt lifecycle and validity
* **Idempotency** - Prevent duplicate receipt creation
## Endpoints
### List Receipts
```http theme={null}
GET /receipts
```
Retrieve a paginated list of receipts.
**Query Parameters:**
* `limit` (integer, 1-100) - Number of results per page (default: 10)
* `next` (string) - Pagination cursor for next page
* `team` (string) - gigstack Connect: Target team ID
* `order_by` (string) - Field to sort by (default: `timestamp`)
* `sort` (string) - Sort direction (`asc`, `desc`)
* `created[gte]` / `created[gt]` / `created[lte]` / `created[lt]` - Filter by creation date (epoch ms, seconds, or ISO date)
* `client_id` (string) - Filter by the gigstack client ID
* `tax_id` (string) - Filter by the client's tax ID / RFC (e.g., `tax_id=PEGJ800101ABC`)
`status`, `valid_until` and `metadata` are accepted but **ignored** by this endpoint — they do not narrow the results. Filter on them client-side.
**Example Request:**
```bash theme={null}
# All receipts for a given RFC
curl -X GET "https://api.gigstack.io/v2/receipts?tax_id=PEGJ800101ABC&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
# Receipts for a specific client ID
curl -X GET "https://api.gigstack.io/v2/receipts?client_id=client_xxx&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Receipts retrieved successfully",
"data": [
{
"id": "receipt_1234567890",
"client": {
"id": "client_1234567890",
"name": "Juan Pérez García",
"tax_id": "PEGJ800101ABC"
},
"status": "pending",
"total": 1160.0,
"subtotal": 1000.0,
"taxes": 160.0,
"currency": "MXN",
"periodicity": "month",
"payment_form": "03",
"created": 1677651234,
"validUntil": 1680243234,
"url": "https://invoicing.gigstack.pro/autofactura?id=receipt_1234567890"
}
],
"has_more": false,
"total_results": 1
}
```
### Create Receipt
```http theme={null}
POST /receipts
```
Create a new receipt with items and client information. Receipts are pre-invoice documents that can be later stamped as CFDI invoices.
**Key Features:**
* Automatic amount calculations with taxes
* Flexible validity periods
* Client auto-creation support
* Metadata support for tracking
* Custom payment form codes
* Idempotency support to prevent duplicate receipts
**Request Body:**
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"exchange_rate": 1.0,
"items": [
{
"id": "service_1234567890",
"quantity": 2,
"unit_price": 1000.0
}
],
"periodicity": "month",
"payment_form": "03",
"idempotency_key": "receipt-key-12345",
"metadata": {
"order_id": "ORD-12345",
"department": "Sales",
"project_code": "PROJ-2024-Q1",
"external_reference": "EXT-ABC-789",
"priority": "high",
"custom_tags": ["recurring", "priority-client"]
}
}
```
**Optional Fields:**
* `idempotency_key` (string) - Unique key to prevent duplicate receipt creation. If a receipt with this key already exists, the existing receipt will be returned instead of creating a duplicate.
* `exchange_rate` (number) - Exchange rate for currency conversion. If not provided, the latest rate from our rates collection will be used automatically.
#### Periodicity Options
> ### ⚠️ On receipts the value is `two_month` — singular
>
> The receipts endpoints accept exactly these five values:
>
> ```
> day | week | two_weeks | month | two_month
> ```
>
> **Team settings use `two_months` — plural** (`PUT /v2/teams/{id}/settings`, field `defaults.periodicity`). The two endpoints genuinely disagree, and neither one accepts the other's spelling. This is not a typo in the docs; it is the current state of the API, kept as-is because normalizing it would break live integrations. Do not copy a periodicity value from one endpoint's payload into the other's — look it up.
>
> Sending `two_months` to a receipts endpoint fails validation with a `400` naming the allowed enum values.
| Value | Receipt is valid until |
| - | - |
| `day` | End of the current day |
| `week` | End of the current week |
| `two_weeks` | 2 weeks from creation, end of that day |
| `month` | End of the current month — **the default** when `periodicity` is omitted |
| `two_month` | End of the current month |
> **`two_month` currently behaves the same as `month`.** The validity calculation is keyed on the plural spelling, so the singular value the schema accepts falls through to the default branch and yields end-of-current-month rather than end-of-next-month. If you need a receipt to stay valid into the following month, set `invoice_config.validUntil` explicitly instead of relying on `two_month`.
**Payment Form Codes:**
* `01` - Cash
* `02` - Check
* `03` - Electronic transfer
* `04` - Credit card
* `05` - Electronic money
* `06` - Digital money
* `99` - To be defined
**Idempotency:**
The `idempotency_key` parameter allows you to safely retry receipt creation requests without creating duplicate receipts. This is especially useful for handling network errors or ensuring exactly-once semantics in distributed systems.
* If you provide an `idempotency_key` and a receipt with that key already exists, the API will return the existing receipt instead of creating a new one
* Idempotency keys should be unique per receipt creation attempt
* Common patterns: use UUIDs, combine order ID with timestamp, or use external system references
**Example with Client Auto-Creation:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/receipts \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"auto_create": true,
"safety_check": false
},
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601"
},
"currency": "MXN",
"items": [
{
"description": "Professional services",
"quantity": 1,
"unit_price": 5000.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
],
"periodicity": "month",
"payment_form": "03"
}'
```
### Get Receipt
```http theme={null}
GET /receipts/{id}
```
Retrieve a specific receipt by ID.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/receipts/receipt_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Stamp Receipt
```http theme={null}
POST /receipts/{id}/stamp
```
Convert a receipt into a CFDI invoice by stamping it with SAT.
**Stamp Options:**
* `client` - Stamp to the associated client
* `general_public_national` - Stamp to Mexican general public
* `general_public_foreign` - Stamp to foreign general public
**Request Body:**
```json theme={null}
{
"stamp_to": "client",
"fiscal_information": {
"legal_name": "Juan Pérez García",
"tax_id": "PEGJ800101ABC",
"tax_system": "601",
"zip": "01000"
},
"date": 1677651234000
}
```
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/receipts/receipt_1234567890/stamp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"stamp_to": "client",
"date": 1677651234000
}'
```
### Reopen Receipt
```http theme={null}
POST /receipts/{id}/reopen
```
Return a receipt whose self-invoicing failed to `pending`, so the client can try again from the self-invoicing portal. Clears `automatic_invoice_error`.
Only a receipt that is **not** `pending` and has **no** entries in `invoices[]` can be reopened. An already-pending receipt, or one that produced a CFDI, answers `409` — reopening never cancels a CFDI. The optional `reason` (up to 500 characters) is kept on the receipt for your own audit trail and is not returned by the API.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/receipts/receipt_1234567890/reopen \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reason": "El cliente pidió corregir su RFC" }'
```
Answers `200` with the receipt in `data`, back at `status: pending`.
### Cancel Receipt
```http theme={null}
DELETE /receipts/{id}
```
Cancel a receipt. This action cannot be undone.
**Example Request:**
```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/receipts/receipt_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Receipt Structure
### Receipt Status
| Status | Description |
| - | - |
| `pending` | Receipt created, awaiting stamping |
| `stamped` | Receipt converted to CFDI invoice |
| `expired` | Receipt validity period expired |
| `canceled` | Receipt canceled |
### Validity Periods
Receipts have configurable validity periods based on the `periodicity` parameter:
* **day**: Valid until end of creation day
* **week**: Valid until end of creation week
* **two\_weeks**: Valid for 14 days from creation
* **month**: Valid until end of creation month (default)
* **two\_month** (singular — see the [box above](#periodicity-options)): accepted, but currently resolves to end of creation month, same as `month`
Anything not in that list is rejected. In particular `two_months` (plural) is a **team-settings** value, not a receipts value.
When the exact expiry matters, set `invoice_config.validUntil` (epoch ms) rather than deriving it from `periodicity`.
## Complex Receipt Examples
### Receipt with Multiple Items
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"items": [
{
"id": "service_001",
"quantity": 2,
"unit_price": 1000.0
},
{
"description": "Installation service",
"quantity": 1,
"unit_price": 500.0,
"product_key": "72121400",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
],
"periodicity": "two_weeks",
"payment_form": "04"
}
```
### Receipt with USD Currency and Exchange Rate
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"currency": "USD",
"exchange_rate": 18.5,
"items": [
{
"description": "International consulting",
"quantity": 10,
"unit_price": 100.0,
"product_key": "80141503",
"unit_key": "HUR",
"taxes": [
{
"type": "IVA",
"rate": 0.0,
"factor": "Exento"
}
]
}
],
"periodicity": "month",
"payment_form": "03"
}
```
### Receipt with Custom Invoice Configuration
```json theme={null}
{
"client": {
"id": "client_1234567890"
},
"currency": "MXN",
"items": [
{
"id": "service_1234567890",
"quantity": 1
}
],
"periodicity": "month",
"payment_form": "03",
"invoice_config": {
"serie": "B",
"folio": 456
},
"metadata": {
"project_id": "PROJ-2024-001",
"department": "Engineering"
}
}
```
## Enhanced Metadata Support
The receipts API now supports flexible metadata storage that preserves all custom properties you provide. This enhancement allows you to:
* **Store Any Custom Properties** - Add any key-value pairs relevant to your business
* **Preserve Data Structure** - All metadata properties are returned exactly as submitted
* **Track Business References** - Store external IDs, project codes, and system identifiers
* **Support Complex Data** - Use nested objects, arrays, and mixed data types
### Metadata Usage Examples
**Basic Tracking:**
```json theme={null}
{
"metadata": {
"order_id": "ORD-12345",
"department": "Sales"
}
}
```
**Advanced Tracking:**
```json theme={null}
{
"metadata": {
"project": {
"code": "PROJ-2024-Q1",
"manager": "Alice Smith",
"budget_category": "consulting"
},
"external_systems": {
"crm_id": "CRM-789",
"erp_reference": "ERP-ABC-123"
},
"tags": ["priority", "recurring", "b2b"],
"workflow_stage": "approved",
"custom_fields": {
"delivery_date": "2024-03-15",
"special_instructions": "Rush order"
}
}
}
```
**Integration Identifiers:**
```json theme={null}
{
"metadata": {
"stripe_session_id": "cs_test_123",
"shopify_order_id": "shop_456",
"quickbooks_reference": "QB-789",
"internal_workflow_id": "WF-2024-001"
}
}
```
## Common Scenarios
### 1. Create Monthly Receipt (Standard)
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/receipts \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"currency": "MXN",
"items": [{
"id": "service_1234567890",
"quantity": 1
}],
"periodicity": "month",
"payment_form": "03",
"idempotency_key": "receipt-2024-0123",
"metadata": {
"order_reference": "ORD-2024-0123"
}
}'
```
### 2. Create Weekly Receipt
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/receipts \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"currency": "MXN",
"items": [{
"description": "Weekly rental",
"quantity": 1,
"unit_price": 2000.00,
"product_key": "81111500",
"unit_key": "DAY",
"taxes": [{"type": "IVA", "rate": 0.16}]
}],
"periodicity": "week",
"payment_form": "01"
}'
```
### 3. Stamp Receipt to Client
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/receipts/receipt_1234567890/stamp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"stamp_to": "client"
}'
```
### 4. Stamp to General Public
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/receipts/receipt_1234567890/stamp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"stamp_to": "general_public_national",
"fiscal_information": {
"legal_name": "Público en General",
"tax_id": "XAXX010101000",
"tax_system": "616",
"zip": "01000"
}
}'
```
## Receipt Workflows
### Standard Receipt Workflow
```mermaid theme={null}
graph LR
A[Create Receipt] --> B[Receipt Pending]
B --> C[Customer Views Receipt]
C --> D[Stamp as CFDI]
D --> E[Invoice Generated]
```
### Receipt with Auto-Client Creation
```mermaid theme={null}
graph LR
A[Create Receipt] --> B[Search Client]
B --> C{Client Found?}
C -->|No| D[Create Client]
C -->|Yes| E[Use Existing]
D --> F[Create Receipt]
E --> F
```
### Receipt Validity Management
```mermaid theme={null}
graph LR
A[Receipt Created] --> B[Set Validity Period]
B --> C{Within Period?}
C -->|Yes| D[Can Stamp]
C -->|No| E[Expired]
D --> F[Stamp as CFDI]
```
## Best Practices
1. **Set appropriate periodicity** - Choose validity periods based on your workflow
2. **Leverage enhanced metadata** - Store any custom data, external references, and business identifiers; all properties are preserved exactly as submitted
3. **Include complete item information** - Ensure accurate calculations
4. **Validate clients first** - Check fiscal data before creating receipts
5. **Monitor receipt expiration** - Stamp receipts before they expire
6. **Handle exchange rates** - Set proper rates for foreign currency receipts
7. **Test stamping process** - Validate CFDI generation in staging
8. **Structure metadata thoughtfully** - Use consistent naming conventions and organize data logically for easy retrieval and filtering
9. **Use idempotency keys** - Always provide an `idempotency_key` when creating receipts to prevent duplicates, especially when retrying failed requests or processing webhook events
## Related Resources
* [Clients API](/guides/clients) - Manage receipt recipients
* [Services API](/guides/services) - Configure receipt items
* [Payments API](/guides/payments) - Process payments for receipts
* [Invoices API](/guides/invoices) - View stamped receipts as invoices
## Error Handling
### At a glance
| Status | When | Retry the same request? | What you should do |
| - | - | - | - |
| `400` | Body validation, wrong `periodicity` spelling, bad `stamp_to` combination, cancelling a non-pending or expired receipt | No | Fix the payload — see below. |
| `401` | Missing/invalid token, or no resolvable team | No | Re-authenticate. |
| `403` | The receipt belongs to another team | No | Use `?team=` (gigstack Connect). |
| `404` | Receipt or its client not found, or the receipt's `livemode` does not match the key | No | Check the id, and that you are using the key for that environment. |
| `409` | A concurrent request is auto-creating the same client/service | **Yes**, after a short backoff | Retry once. |
| `429` | **Team credit limit reached** on `POST /receipts` | No, until raised | See below. |
| `500` | Downstream stamping service failed | Maybe | See below. |
### 429 — Team credit limit reached
`POST /v2/receipts` consumes a team credit **before** the receipt is created:
```json theme={null}
{
"success": false,
"error": {
"code": "team_credit_limit_reached",
"message": "Team credit limit reached",
"details": "Team credit limit reached (500/500)"
},
"timestamp": 1718451000000
}
```
Not a rate limit — backing off will not help. Raise `credit_limit` on the team (`PUT /v2/teams/{id}`) or contact gigstack. Nothing was created and nothing was charged.
> The invoice endpoints report the same condition with a different body (`credit_limit` / `used_credits` at the top level). See [Invoices → 429](/guides/invoices#429-—-team-credit-limit-reached).
### 400 — Invalid periodicity
```json theme={null}
{
"success": false,
"error": {
"code": "validation_failed",
"message": "Invalid request body",
"details": ["periodicity: must be one of day, week, two_weeks, month, two_month"]
}
}
```
Almost always caused by sending `two_months` (plural), which is the **team-settings** spelling. See the [periodicity box](#periodicity-options).
### 400 — Invalid `stamp_to` combination
`POST /receipts/{id}/stamp` enforces two rules:
```json theme={null}
{
"error": "Invalid request",
"message": "fiscal_information cannot be provided when stamp_to is general_public"
}
```
```json theme={null}
{
"error": "Missing client information",
"message": "fiscal_information is required when stamp_to is client and receipt has no client"
}
```
**Do:** for `general_public_national` / `general_public_foreign`, omit `fiscal_information` entirely — gigstack supplies the SAT generic receiver. For `stamp_to: "client"`, either the receipt must already reference a client, or you must pass `fiscal_information` yourself.
### 404 — Client not found while stamping
```json theme={null}
{
"error": "Client not found",
"message": "Client not found in database"
}
```
The receipt references a client id that no longer exists. Re-stamp passing `fiscal_information` explicitly, or recreate the client.
### 400 — Cannot cancel
`DELETE /receipts/{id}` only accepts receipts that are still open:
```json theme={null}
{
"success": false,
"error": {
"code": "invalid_request_body",
"message": "Cannot cancel receipt with status: completed. Only pending receipts can be cancelled."
}
}
```
```json theme={null}
{
"success": false,
"error": {
"code": "invalid_request_body",
"message": "Cannot cancel receipt that has already expired (validUntil is in the past)."
}
}
```
Both are terminal — an already-stamped or already-expired receipt cannot be cancelled through the API. If a stamped receipt produced an invoice you need to void, cancel the **invoice** instead (`DELETE /v2/invoices/{id}`).
### 500 — Stamping failed downstream
```json theme={null}
{
"error": "…the underlying service's message…",
"message": "Failed to process receipt: 400 Bad Request"
}
```
The receipt reached the stamping service and it refused. The `error` field carries the real reason — most often incomplete fiscal data on the resolved receiver (missing `tax_system`, `zip`, or an RFC the SAT rejects). The receipt stays `pending`, so it is safe to fix the client and stamp again.
### Failed Client Fiscal Information
When a client's fiscal information fails validation (invalid RFC or EFOS blacklist), receipt creation is rejected with `400`. Update the client (`PUT /v2/clients/{id}`) and retry.
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Retentions API Guide
Source: https://docs.gigstack.io/guides/retentions
Integration guide for Retentions
Create, manage, and cancel CFDI retention documents (Constancias de Retenciones e Informacion de Pagos) with full SAT compliance. The Retentions API handles the complete lifecycle of withholding tax certificates required by Mexican tax law.
## Overview
Retentions are CFDI documents that certify taxes withheld by a payer on behalf of a recipient. They are required in scenarios such as interest payments, technology platform services, dividends, royalties, and other transactions where the payer is legally obligated to withhold taxes (ISR, IVA, IEPS) and report them to SAT.
Unlike standard invoices, retentions have a distinct structure: they are organized by retention key (type of operation), fiscal period, and withheld tax amounts rather than line items.
## Key Features
* **SAT Retention Keys** - Support for all 26 retention types (01-26)
* **Automatic Tax Mapping** - Friendly names (ISR, IVA, IEPS) mapped to SAT codes
* **Auto-Calculated Totals** - Taxable, exempt, and retained amounts computed automatically
* **Complement Support** - Built-in handling for Intereses (key 16), Plataformas Tecnologicas (key 26), and Otro tipo (key 25)
* **Folio Management** - Automatic folio generation and stamping
* **File Generation** - PDF and XML files generated on stamp
* **Cancellation** - SAT-compliant cancellation with motive codes
## Endpoints
### List Retentions
```http theme={null}
GET /retentions
```
Retrieve a paginated list of retention documents with filtering capabilities.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `next` | string | Pagination cursor for next page |
| `team` | string | gigstack Connect: Target team ID |
| `order_by` | string | Field to sort by (default: `created_at`) |
| `sort` | string | Sort direction: `asc` or `desc` |
| `created_gte` | integer | Filter by creation date (greater than or equal, unix timestamp ms) |
| `created_lte` | integer | Filter by creation date (less than or equal, unix timestamp ms) |
| `status` | string | Filter by status: `valid` or `canceled` |
| `retention_key` | string | Filter by retention type key (e.g., `14`, `16`, `26`) |
| `client_id` | string | Filter by client ID (retentions created before this field was added won't match) |
| `tax_id` | string | Filter by the client's tax ID / RFC (e.g., `tax_id=PEGJ800101ABC`) |
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/retentions?status=valid&retention_key=14&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Retentions retrieved successfully",
"data": [
{
"id": "ret_abc123",
"uuid": "12345678-1234-1234-1234-123456789012",
"status": "valid",
"document_type": "retenciones",
"retention_key": "14",
"folio_number": "1",
"series": "RET",
"receiver": {
"legal_name": "Juan Perez Garcia",
"tax_id": "PEGJ800101ABC",
"nationality": "Nacional"
},
"period": {
"start": 1,
"end": 12,
"year": 2025
},
"totals": {
"total_operation": 100000.00,
"total_taxable": 100000.00,
"total_exempt": 0,
"total_retained": 10000.00,
"tax_retained": [
{
"tax": "ISR",
"base": 100000.00,
"amount": 10000.00,
"payment_type": "03"
}
]
},
"stamp": {
"uuid": "12345678-1234-1234-1234-123456789012",
"stamped_at": "2025-06-15T10:30:00Z"
},
"livemode": true,
"created_at": 1718451000000
}
],
"has_more": false,
"total_results": 1
}
```
### Create Retention
```http theme={null}
POST /retentions
```
Create and stamp a new retention CFDI document. The retention is normalized, stamped with SAT, and saved in a single operation.
**Request Body:**
| Field | Type | Required | Description |
| - | - | - | - |
| `retention_key` | string | Yes | SAT retention type code (01-26) |
| `client` | object | Yes | Client reference (by ID, search, or inline data) |
| `period_start` | number | Yes | Start month of the fiscal period (1-12) |
| `period_end` | number | Yes | End month of the fiscal period (1-12) |
| `period_year` | number | Yes | Fiscal year |
| `total_operation` | number | Yes | Total operation amount |
| `taxes` | array | Yes | Array of retained taxes |
| `total_exempt` | number | No | Exempt amount (default: 0) |
| `series` | string | No | Invoice series (default: "RET") |
| `metadata` | object | No | Custom metadata key-value pairs |
| `idempotency_key` | string | No | Unique key to prevent duplicate creation |
| `retention_description` | string | No | Required for key "25" (Otro tipo de retenciones) |
| `interest` | object | No | Required for key "16" (Intereses) |
| `platform_services` | object | No | Required for key "26" (Plataformas Tecnologicas) |
**Tax Object:**
```json theme={null}
{
"tax": "ISR",
"base": 100000.00,
"amount": 10000.00,
"payment_type": "03"
}
```
* `tax` (string, required) - Tax type: `ISR`, `IVA`, or `IEPS`. Mapped automatically to SAT codes (001, 002, 003).
* `base` (number, required) - Taxable base amount.
* `amount` (number, required) - Amount retained.
* `payment_type` (string, optional) - SAT payment type code. Defaults to `03` (provisional) for ISR and `01` (definitivo) for IVA/IEPS.
**Common Retention Keys:**
| Key | Description | Complement Required |
| - | - | - |
| `01` | Servicios profesionales | No |
| `02` | Renta de inmuebles | No |
| `06` | Enajenacion de acciones | No |
| `14` | Dividendos o utilidades | No |
| `16` | Intereses | Yes (`interest` object) |
| `25` | Otro tipo de retenciones | Yes (`retention_description`) |
| `26` | Plataformas tecnologicas | Yes (`platform_services` object) |
#### Required combinations per retention key
A miss is a hard `400`.
Three retention keys carry a complement, and the API validates the combination **before** anything is sent to the PAC. Nothing is stamped, nothing is charged, and the response is `400` with the exact message below in `error.message`:
| If `retention_key` is | You must also send | Message when you don't |
| - | - | - |
| `"16"` (Intereses) | `interest` object | `interest object is required for retention key 16 (Intereses)` |
| `"25"` (Otro tipo de retenciones) | `retention_description` string | `retention_description is required for retention key 25 (Otro tipo de retenciones)` |
| `"26"` (Plataformas Tecnológicas) | `platform_services` object | `platform_services object is required for retention key 26 (Plataformas Tecnológicas)` |
| `"26"` (Plataformas Tecnológicas) | **and** at least one tax in `taxes` whose `tax` is not `IVA` — i.e. `ISR` or `IEPS` | `At least one non-IVA tax (ISR or IEPS) is required for Plataformas Tecnológicas` |
Notes that catch people out:
* `retention_key` is a **string**, not a number. `"16"` matches; `16` does not, and the complement check silently does not apply.
* The extra rule on key `26` is easy to miss: an IVA-only `taxes` array passes the schema and is then rejected by the validator. A Plataformas Tecnológicas retention is expected to retain ISR (and may also retain IVA), so send both.
* These three fields are schema-optional at the top level precisely because they are conditional — the schema will not catch a missing one, only this validator will.
* Every other key (`01`, `02`, `06`, `14`, …) needs no complement; sending one anyway is simply ignored for that key.
Worked examples for all three follow: [key 16](#example-retention-with-interest-complement-key-16), [key 25](#example-custom-retention-type-key-25), [key 26](#example-platform-services-retention-key-26).
#### Example: Basic Retention (Dividends)
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/retentions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"retention_key": "14",
"client": {
"id": "client_1234567890"
},
"period_start": 1,
"period_end": 12,
"period_year": 2025,
"total_operation": 500000.00,
"total_exempt": 0,
"taxes": [
{
"tax": "ISR",
"base": 500000.00,
"amount": 50000.00
}
],
"metadata": {
"dividend_resolution": "ACT-2025-001"
}
}'
```
#### Example: Retention with Interest Complement (Key 16)
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/retentions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"retention_key": "16",
"client": {
"id": "client_1234567890"
},
"period_start": 1,
"period_end": 6,
"period_year": 2025,
"total_operation": 250000.00,
"taxes": [
{
"tax": "ISR",
"base": 250000.00,
"amount": 25000.00
}
],
"interest": {
"financial_system": "02",
"nominal_interest": 18500.00,
"real_interest": 12300.00,
"loss": 0
}
}'
```
#### Example: Platform Services Retention (Key 26)
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/retentions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"retention_key": "26",
"client": {
"search": {
"on_key": "tax_id",
"on_value": "PEGJ800101ABC",
"auto_create": true
},
"name": "Juan Perez Garcia",
"email": "juan@example.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "625"
},
"period_start": 3,
"period_end": 3,
"period_year": 2025,
"total_operation": 45000.00,
"taxes": [
{
"tax": "ISR",
"base": 45000.00,
"amount": 4500.00
},
{
"tax": "IVA",
"base": 45000.00,
"amount": 7200.00
}
],
"platform_services": {
"periodicity": "04",
"services": [
{
"payment_form": "03",
"service_type": "01",
"service_date": "2025-03-15",
"price_without_tax": 25000.00,
"tax_rate": 0.16,
"commission": 2500.00
},
{
"payment_form": "04",
"service_type": "01",
"service_date": "2025-03-22",
"price_without_tax": 20000.00,
"tax_rate": 0.16,
"commission": 2000.00
}
]
}
}'
```
#### Example: Custom Retention Type (Key 25)
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/retentions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"retention_key": "25",
"client": {
"id": "client_1234567890"
},
"period_start": 1,
"period_end": 12,
"period_year": 2025,
"total_operation": 120000.00,
"taxes": [
{
"tax": "ISR",
"base": 120000.00,
"amount": 12000.00
}
],
"retention_description": "Retencion por servicios de asesoria fiscal especializada"
}'
```
**Response (201):**
```json theme={null}
{
"message": "Retention created successfully",
"data": {
"id": "ret_xyz789",
"uuid": "98765432-1234-1234-1234-123456789012",
"status": "valid",
"document_type": "retenciones",
"retention_key": "14",
"folio_number": "1",
"series": "RET",
"issuer": {
"legal_name": "Mi Empresa SA de CV",
"tax_id": "MEM200101ABC",
"tax_system": "601"
},
"receiver": {
"legal_name": "Juan Perez Garcia",
"tax_id": "PEGJ800101ABC",
"nationality": "Nacional"
},
"period": {
"start": 1,
"end": 12,
"year": 2025
},
"totals": {
"total_operation": 500000.00,
"total_taxable": 500000.00,
"total_exempt": 0,
"total_retained": 50000.00,
"tax_retained": [
{
"tax": "ISR",
"base": 500000.00,
"amount": 50000.00,
"payment_type": "03"
}
]
},
"stamp": {
"uuid": "98765432-1234-1234-1234-123456789012",
"stamped_at": "2025-06-15T10:30:00Z"
},
"verification_url": "https://verificacfdi.facturaelectronica.sat.gob.mx/...",
"livemode": true,
"created_at": 1718451000000,
"metadata": {
"dividend_resolution": "ACT-2025-001"
}
}
}
```
### Get Retention
```http theme={null}
GET /retentions/{id}
```
Retrieve a specific retention document by its UUID.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/retentions/98765432-1234-1234-1234-123456789012 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Get Retention Files
```http theme={null}
GET /retentions/{id}/files
```
Retrieve the PDF and XML files for a stamped retention.
**Query Parameters:**
* `file_type` (string, optional) - Filter by file type: `pdf` or `xml`. Returns both if omitted.
* `team` (string, optional) - gigstack Connect: Target team ID.
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/retentions/98765432-1234-1234-1234-123456789012/files?file_type=pdf" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Retention files retrieved successfully",
"data": [
{
"content": "JVBERi0xLjQK...",
"filename": "retention_98765432.pdf",
"type": "application/pdf"
}
]
}
```
### Cancel Retention
```http theme={null}
DELETE /retentions/{id}
```
Cancel a stamped retention with SAT. Requires a cancellation motive.
**Request Body:**
```json theme={null}
{
"motive": "02",
"replace_uuid": null
}
```
**Cancellation Motives:**
| Code | Description | Requires `replace_uuid` |
| - | - | - |
| `01` | Comprobante emitido con errores con relacion | Yes |
| `02` | Comprobante emitido con errores sin relacion | No |
| `03` | No se llevo a cabo la operacion | No |
| `04` | Operacion nominativa relacionada en factura global | No |
**Example Request:**
```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/retentions/98765432-1234-1234-1234-123456789012 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"motive": "02"
}'
```
**Example Response:**
```json theme={null}
{
"message": "Retention cancelled successfully",
"data": {
"id": "ret_xyz789",
"uuid": "98765432-1234-1234-1234-123456789012",
"status": "canceled",
"cancellation": {
"motive": "02",
"canceled_at": "2025-06-20T15:00:00Z"
}
}
}
```
## Retention Structure
### Retention Keys (Types)
The `retention_key` identifies the type of operation that requires tax withholding. Common keys include:
| Key | Description |
| - | - |
| `01` | Servicios profesionales |
| `02` | Renta de inmuebles |
| `03` | Comercio exterior |
| `04` | Fideicomisos que no realizan actividades empresariales |
| `05` | Planes de retiro |
| `06` | Enajenacion de acciones |
| `07` | Intereses reales hipotecarios |
| `08` | Interes real por primas de seguro |
| `09` | Premios |
| `10` | Otras retenciones por ganancias |
| `11` | Pagos a extranjeros |
| `12` | Pagos a extranjeros consolidados |
| `14` | Dividendos o utilidades |
| `15` | Remanente distribuido |
| `16` | Intereses |
| `17` | Arrendamiento en fideicomiso |
| `18` | Pagos a FIBRAS |
| `22` | Enajenacion de FIBRAS |
| `25` | Otro tipo de retenciones |
| `26` | Plataformas tecnologicas |
### Tax Types
| Friendly Name | SAT Code | Default Payment Type |
| - | - | - |
| `ISR` | 001 | `03` (Pago provisional) |
| `IVA` | 002 | `01` (Pago definitivo) |
| `IEPS` | 003 | `01` (Pago definitivo) |
### Auto-Calculated Fields
When creating a retention, the following fields are computed automatically:
* **total\_taxable** = `total_operation` - `total_exempt`
* **total\_retained** = sum of all `taxes[].amount`
You do not need to send these values; they are derived from the input.
## Complement-Specific Fields
### Interest Complement (Key 16)
Required when `retention_key` is `"16"`. Provides details about financial interest payments.
```json theme={null}
{
"interest": {
"financial_system": "02",
"nominal_interest": 18500.00,
"real_interest": 12300.00,
"loss": 0,
"withdrawal_aores": null,
"financial_derivatives": null
}
}
```
| Field | Type | Required | Description |
| - | - | - | - |
| `financial_system` | string | Yes | Financial system code |
| `nominal_interest` | number | Yes | Nominal interest amount |
| `real_interest` | number | Yes | Real (inflation-adjusted) interest amount |
| `loss` | number | No | Loss amount (default: 0) |
| `withdrawal_aores` | string | No | AORES withdrawal code |
| `financial_derivatives` | string | No | Financial derivatives code |
### Platform Services Complement (Key 26)
Required when `retention_key` is `"26"`. Details technology platform service transactions.
```json theme={null}
{
"platform_services": {
"periodicity": "04",
"services": [
{
"payment_form": "03",
"service_type": "01",
"service_date": "2025-03-15",
"price_without_tax": 25000.00,
"tax_rate": 0.16,
"commission": 2500.00,
"government_contribution": 0
}
]
}
}
```
**Periodicity Codes:**
| Code | Description |
| - | - |
| `01` | Diario |
| `02` | Semanal |
| `03` | Quincenal |
| `04` | Mensual |
| `05` | Bimestral |
### Custom Retention Description (Key 25)
Required when `retention_key` is `"25"`. A free-text description of the retention type.
```json theme={null}
{
"retention_description": "Retencion por servicios de asesoria fiscal especializada"
}
```
## Use Cases
### 1. Dividend Distribution
A company distributes dividends to shareholders and must issue retention certificates for the ISR withheld.
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/retentions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"retention_key": "14",
"client": {"id": "client_shareholder_001"},
"period_start": 1,
"period_end": 12,
"period_year": 2025,
"total_operation": 1000000.00,
"taxes": [{"tax": "ISR", "base": 1000000.00, "amount": 100000.00}],
"metadata": {"resolution": "AG-2025-003", "shares": 5000}
}'
```
### 2. Technology Platform (Uber, Rappi, etc.)
A technology platform withholds taxes on behalf of service providers and must issue monthly retention certificates.
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/retentions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"retention_key": "26",
"client": {
"search": {"on_key": "tax_id", "on_value": "GARJ900515ABC", "auto_create": true},
"name": "Jose Garcia Rodriguez",
"tax_id": "GARJ900515ABC",
"tax_system": "625"
},
"period_start": 6,
"period_end": 6,
"period_year": 2025,
"total_operation": 35000.00,
"taxes": [
{"tax": "ISR", "base": 35000.00, "amount": 3500.00},
{"tax": "IVA", "base": 35000.00, "amount": 2800.00}
],
"platform_services": {
"periodicity": "04",
"services": [
{"payment_form": "03", "service_type": "01", "service_date": "2025-06-10", "price_without_tax": 18000.00, "tax_rate": 0.16, "commission": 1800.00},
{"payment_form": "03", "service_type": "01", "service_date": "2025-06-25", "price_without_tax": 17000.00, "tax_rate": 0.16, "commission": 1700.00}
]
}
}'
```
### 3. Professional Services Withholding
A company paying a freelance consultant withholds ISR and IVA as required by law.
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/retentions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"retention_key": "01",
"client": {"id": "client_consultant_001"},
"period_start": 1,
"period_end": 3,
"period_year": 2025,
"total_operation": 150000.00,
"taxes": [
{"tax": "ISR", "base": 150000.00, "amount": 15000.00},
{"tax": "IVA", "base": 150000.00, "amount": 16000.00}
]
}'
```
### 4. Bank Interest Payments
A financial institution issues retention certificates for interest paid to account holders.
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/retentions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"retention_key": "16",
"client": {"id": "client_account_holder"},
"period_start": 1,
"period_end": 12,
"period_year": 2025,
"total_operation": 85000.00,
"taxes": [{"tax": "ISR", "base": 85000.00, "amount": 8500.00}],
"interest": {
"financial_system": "02",
"nominal_interest": 12750.00,
"real_interest": 8500.00
}
}'
```
## Best Practices
1. **Use the correct retention key** - Each type of withholding operation has a specific key. Using the wrong key will cause SAT validation errors.
2. **Provide complement data when required** - Keys 16, 25, and 26 require additional fields. The API will reject requests missing required complement data.
3. **Verify client fiscal data** - The receiver's RFC and tax system must be valid and registered with SAT.
4. **Use idempotency keys** - Prevent duplicate retentions when retrying failed requests.
5. **Match fiscal periods accurately** - The `period_start`, `period_end`, and `period_year` must correspond to the actual period covered by the retention.
6. **Review before stamping** - Unlike draft invoices, retentions are stamped immediately on creation. Verify all data before submitting.
7. **Keep metadata organized** - Use metadata to link retentions to internal records (resolutions, contracts, account numbers).
## Related Resources
* [Clients API](/guides/clients) - Manage retention recipients
* [Invoices API](/guides/invoices) - Standard CFDI invoicing
* [Teams API](/guides/teams) - Configure team SAT certificates
* [CFDI Errors Reference](/guides/catalogs/cfdi_errors) - Error codes for stamping failures
## Error Handling
### Missing Required Fields
```json theme={null}
{
"message": "Invalid request body",
"error": "retention_key is required"
}
```
### Invalid Retention Key
```json theme={null}
{
"message": "Invalid request body",
"error": "retention_key must be a valid SAT retention type (01-26)"
}
```
### Missing Complement Data (400)
Returned by the conditional-requirements check described under [Required combinations](#required-combinations-per-retention-key). The status is always `400` and the offending rule is spelled out verbatim in `error.message`:
```json theme={null}
{
"success": false,
"error": {
"code": "invalid_request_body",
"message": "interest object is required for retention key 16 (Intereses)"
},
"timestamp": 1718451000000
}
```
The other three messages you can get here:
* `retention_description is required for retention key 25 (Otro tipo de retenciones)`
* `platform_services object is required for retention key 26 (Plataformas Tecnológicas)`
* `At least one non-IVA tax (ISR or IEPS) is required for Plataformas Tecnológicas`
**What to do:** add the missing field and retry with the *same* `idempotency_key` — the request never reached the PAC, so no folio was consumed and no document exists.
### Retention Not Found
```json theme={null}
{
"message": "Resource not found"
}
```
### Already Canceled
```json theme={null}
{
"message": "Retention is already canceled"
}
```
### Stamping Error
```json theme={null}
{
"message": "An error occurred while creating retention",
"error": {
"code": "CFDI33106",
"message": "El RFC del receptor no es valido"
}
}
```
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# SAT Lists API Guide
Source: https://docs.gigstack.io/guides/sat-lists
Integration guide for SAT Lists
## Overview
The SAT publishes lists of taxpayers under **Artículo 69**, **Artículo 69-B** and **Artículo 69-B Bis** of the Código Fiscal — for example taxpayers with cancelled certificates, taxpayers that could not be located, and companies presumed or confirmed to issue simulated invoices (EFOS). Invoicing a taxpayer on one of the risky lists can cost you the deduction.
gigstack mirrors these lists and re-syncs them from the SAT's published CSVs **every Sunday**, so you can screen a counterparty's RFC with one call. It also consults the SAT's public *Opinión del Cumplimiento* (32-D) service.
Base path: `https://api.gigstack.io/v2/sat-lists`. All endpoints are read-only and require a valid API key.
## Endpoints
### List the Tracked Lists
```http theme={null}
GET /sat-lists
```
Returns every list gigstack tracks and the result of its latest sync.
| Field | Description |
| - | - |
| `key` | Stable list identifier |
| `label` | Name as published by the SAT |
| `source` | `art_69`, `art_69b` or `art_69b_bis` |
| `is_risky` | `true` for lists that indicate a counterparty you should not invoice (e.g. `Cancelados`, `Definitivos 69-B`, `No localizados`, `CSD sin efectos`); `false` for informational lists |
| `filename` | Source CSV published by the SAT |
| `sync` | Latest sync: `last_sync_at`, `last_status` (`ok` \| `error`), `row_count`, `previous_row_count`, … `null` if the list has never synced |
```bash theme={null}
curl https://api.gigstack.io/v2/sat-lists \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Check an RFC
```http theme={null}
GET /sat-lists/check/{rfc}
```
| Parameter | In | Description |
| - | - | - |
| `rfc` | path | RFC to check, 10-13 characters, case-insensitive |
| `risky_only` | query | `true` to return only entries from lists flagged as risky |
```bash theme={null}
curl "https://api.gigstack.io/v2/sat-lists/check/XAXX010101000" \
-H "Authorization: Bearer YOUR_TOKEN"
```
The response `data`:
| Field | Description |
| - | - |
| `rfc` | The RFC checked, uppercased |
| `found` | `true` if the RFC appears on at least one list |
| `is_risky` | `true` if it appears on at least one risky list |
| `risky_lists` | Keys of the risky lists it appears on |
| `entries` | Every matching entry: `list_key`, `list_label`, `source`, `is_risky`, `detail` (extra CSV columns, when published), `last_seen_at` |
An RFC on no list returns `200` with `found: false` — a clean RFC is not a `404`. An RFC that is not 10-13 characters returns `400`.
**Typical use:** before issuing an invoice or registering a new supplier, call this endpoint and block or flag the operation when `is_risky` is `true`.
### Opinión del Cumplimiento (32-D)
```http theme={null}
GET /sat-lists/32d/{rfc}
```
Consults the SAT's public *Opinión del Cumplimiento de Obligaciones Fiscales* service for an RFC.
**Read this before building on it.** The SAT's public service only publishes **positive** opinions, and only for taxpayers who authorized public disclosure. There are exactly two outcomes:
| `status` | `found` | Meaning |
| - | - | - |
| `positiva` | `true` | The SAT publishes a positive opinion. `pdf_url` links to the stored constancia PDF |
| `no_autorizado` | `false` | The SAT publishes nothing for this RFC. **The result is unknown** — it is *not* a negative opinion |
Never present `no_autorizado` to a user as "opinión negativa", "incumplido" or anything equivalent: this service cannot tell you that a taxpayer is non-compliant. The response also includes `checked_at` (epoch ms) and, for `no_autorizado`, the SAT's own `message`.
If the SAT cannot be reached or its page cannot be parsed, the call returns `500`. That is never reported as `no_autorizado`, so a `no_autorizado` is always a real answer from the SAT.
```bash theme={null}
curl https://api.gigstack.io/v2/sat-lists/32d/EKU9003173C9 \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Errors
| Status | When |
| - | - |
| `400` | RFC is not 10-13 characters |
| `500` | Unexpected failure, or the SAT could not be reached (32-D) |
## Related Resources
* [Clients API](/guides/clients) - EFOS checks run when you validate a client
* [Tax Regimes Catalog](/guides/catalogs/tax_regimes)
# Services API Guide
Source: https://docs.gigstack.io/guides/services
Integration guide for Services
Manage your product and service catalog with SAT compliance codes. The Services API handles product/service definitions, tax configurations, and pricing for use in invoices and payments.
## Overview
Services represent the items you sell or provide. Each service includes SAT product keys, unit keys, pricing, and tax configurations required for Mexican tax compliance.
## Key Features
* **SAT Product Keys** - Proper classification for CFDI compliance
* **Tax Configuration** - IVA, ISR, IEPS support with rates
* **SKU Management** - Internal product identification
* **Auto-creation** - Create services during invoice/payment flow
* **Flexible Pricing** - Unit prices with currency support
## Endpoints
### List Services
```http theme={null}
GET /services
```
Retrieve a paginated list of services in your catalog.
**Query Parameters:**
* `limit` (integer, 1-100) - Number of results per page (default: 10)
* `next` (string) - Pagination cursor for next page
* `team` (string) - gigstack Connect: Target team ID
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/services?limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Services retrieved successfully",
"data": [
{
"id": "service_1234567890",
"description": "Consulting services",
"sku": "CONS-001",
"product_key": "80141503",
"unit_key": "E48",
"unit_name": "Servicio",
"unit_price": 1000.0,
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"factor": "Tasa",
"withholding": false
}
],
"team": "team_1234567890",
"created_at": 1677651234
}
],
"has_more": false,
"total_results": 1
}
```
### Create Service
```http theme={null}
POST /services
```
Create a new service in your catalog.
**Request Body:**
```json theme={null}
{
"description": "Professional consulting services",
"sku": "CONS-001",
"product_key": "80141503",
"unit_key": "E48",
"unit_name": "Servicio",
"unit_price": 1500.0,
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"factor": "Tasa",
"base": null,
"inclusive": false,
"withholding": false
}
]
}
```
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/services \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Web development services",
"sku": "WEB-001",
"product_key": "81111500",
"unit_key": "E48",
"unit_name": "Servicio",
"unit_price": 2500.00,
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"factor": "Tasa",
"withholding": false
}
]
}'
```
### Get Service
```http theme={null}
GET /services/{id}
```
Retrieve a specific service by ID.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/services/service_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Update Service
```http theme={null}
PUT /services/{id}
```
Update an existing service's information.
**Example Request:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/services/service_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"unit_price": 1800.00,
"description": "Updated consulting services"
}'
```
### Delete Service
```http theme={null}
DELETE /services/{id}
```
Delete a specific service from your catalog.
**Example Request:**
```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/services/service_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Service Structure
### Important Fields
| Field | Type | Description | Example |
| - | - | - | - |
| `description` | string | Service description | "Consulting services" |
| `sku` | string | Stock Keeping Unit | "CONS-001" |
| `product_key` | string | SAT product key (8 digits) | "80141503" |
| `unit_key` | string | SAT unit key | "E48" |
| `unit_name` | string | Unit display name | "Servicio" |
| `unit_price` | number | Price per unit | 1000.00 |
| `taxes` | array | Tax configuration | See Tax Configuration |
## Tax Configuration
### Tax Object Structure
```json theme={null}
{
"type": "IVA", // Tax type: IVA, ISR, IEPS
"rate": 0.16, // Tax rate (0.16 = 16%)
"factor": "Tasa", // SAT factor type
"base": null, // Taxable base amount (number or null). If null, auto-calculated from item price.
"inclusive": false, // If tax is included in price
"withholding": false // If this is a withholding tax
}
```
### Common Tax Configurations
#### Standard 16% IVA
```json theme={null}
{
"type": "IVA",
"rate": 0.16,
"factor": "Tasa",
"withholding": false
}
```
#### 10% ISR Withholding
```json theme={null}
{
"type": "ISR",
"rate": 0.1,
"factor": "Tasa",
"withholding": true
}
```
#### Zero IVA (Exempt)
```json theme={null}
{
"type": "IVA",
"rate": 0.0,
"factor": "Exento",
"withholding": false
}
```
## Common SAT Product Keys
| Category | Product Key | Description |
| - | - | - |
| **Consulting** | 80101500 | Business consulting |
| **Software** | 81111500 | Software development |
| **Web Services** | 81112100 | Web hosting services |
| **Training** | 86101604 | Training services |
| **Design** | 82121500 | Graphic design |
| **Marketing** | 80141600 | Marketing services |
| **Legal** | 84111500 | Legal services |
| **Accounting** | 84111600 | Accounting services |
| **Medical** | 85121600 | Medical services |
| **Construction** | 72121400 | Construction services |
## Common SAT Unit Keys
| Unit Key | Description | Use Case |
| - | - | - |
| **E48** | Servicio | Services |
| **H87** | Pieza | Individual pieces |
| **EA** | Elemento | Elements/Items |
| **ACT** | Actividad | Activities |
| **MON** | Mes | Monthly services |
| **ANN** | Año | Annual services |
| **HUR** | Hora | Hourly services |
| **DAY** | Día | Daily services |
| **KGM** | Kilogramo | Weight-based |
| **MTR** | Metro | Length-based |
## Search and Auto-Creation
When creating invoices or payments, you can search for existing services or auto-create them:
```json theme={null}
{
"items": [
{
"search": {
"on_key": "sku",
"on_value": "CONS-001",
"auto_create": true
},
"description": "Consulting services",
"sku": "CONS-001",
"product_key": "80141503",
"unit_key": "E48",
"unit_price": 1000.0,
"quantity": 2,
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}
]
}
```
## Use Cases
### 1. Professional Services Company
```json theme={null}
{
"description": "Legal consultation - 1 hour",
"sku": "LEGAL-HR",
"product_key": "84111500",
"unit_key": "HUR",
"unit_name": "Hora",
"unit_price": 3000.0,
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
},
{
"type": "ISR",
"rate": 0.1,
"withholding": true
}
]
}
```
### 2. E-commerce Product
```json theme={null}
{
"description": "Laptop Computer",
"sku": "LAPTOP-001",
"product_key": "43211500",
"unit_key": "H87",
"unit_name": "Pieza",
"unit_price": 15000.0,
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
}
]
}
```
### 3. Subscription Service
```json theme={null}
{
"description": "Software subscription - Monthly",
"sku": "SAAS-MONTHLY",
"product_key": "81111500",
"unit_key": "MON",
"unit_name": "Mes",
"unit_price": 500.0,
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
}
]
}
```
## Best Practices
1. **Always use correct SAT codes** - Ensure product\_key and unit\_key match SAT catalogs
2. **Configure taxes properly** - Include all applicable taxes (IVA, ISR, IEPS)
3. **Use meaningful SKUs** - Create a consistent SKU system for internal tracking
4. **Include detailed descriptions** - Help clients understand what they're paying for
5. **Set up common services** - Pre-create frequently used services
6. **Update prices regularly** - Keep pricing current with market conditions
## Related Resources
* [Invoices API](/guides/invoices) - Use services in invoices
* [Payments API](/guides/payments) - Include services in payment requests
* [Teams API](/guides/teams) - Configure default tax settings
## Error Handling
### Invalid Product Key
```json theme={null}
{
"message": "Invalid product_key",
"error": "Product key must be 8 digits from SAT catalog"
}
```
### Invalid Tax Configuration
```json theme={null}
{
"message": "Invalid tax configuration",
"error": "Tax rate must be between 0 and 1"
}
```
### Service Not Found
```json theme={null}
{
"message": "Service not found",
"error": "The specified service does not exist"
}
```
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Teams API Guide
Source: https://docs.gigstack.io/guides/teams
Integration guide for Teams
Manage teams, configure settings, and control member access. The Teams API handles team creation, configuration, member management, and system-wide settings for invoicing and payments.
## Overview
Teams are organizational units that contain settings, members, and resources. Each team has its own configuration for invoicing, taxes, series, and automation preferences.
## Key Features
* **Team Management** - Create and configure teams with initial member setup
* **Member Administration** - Add/remove team members, bulk import from master team
* **Settings Configuration** - Invoice, tax, and email settings
* **SAT Connection** - Upload CSD certificates for CFDI invoicing
* **Series Management** - Configure invoice series and folios
* **Integration Support** - Connect with external services
* **gigstack Connect** - Enable multi-team access
## Endpoints
### List Teams
```http theme={null}
GET /teams
```
Retrieve a paginated list of teams.
**Query Parameters:**
* `limit` (integer, 1-100) - Number of results per page (default: 10)
* `next` (string) - Pagination cursor for next page
* `team` (string) - gigstack Connect: Target team ID
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/teams?limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"message": "Clients retrieved successfully",
"timestamp": 1768240724433,
"data": [
{
"id": "team_1234567890",
"legal_name": "Empresa de Tecnología S.A. de C.V.",
"address": {
"country": "MEX",
"street": "Av. Insurgentes Sur 456",
"zip": "03100",
"city": "Ciudad de México",
"state": "CDMX",
"exterior": "96",
"interior": "10",
"neighborhood": "Polanco"
},
"brand": {
"alias": "Mi Empresa",
"primary_color": "#007bff",
"secondary_color": "#6c757d",
"logo": "https://example.com/logo.png"
},
"settings": {
"avoid_automations_on_currencies": ["USD", "EUR"],
"default_description": "Default invoice description",
"taxes": [],
"taxes_usd": [],
"emails": {
"invoices_bcc": ["accounting@example.com"],
"avoid_invoice_emails": false,
"avoid_test_invoice_emails": true,
"avoid_receipts_emails": false
},
"override_item_description": "Custom item description",
"global_invoice_disabled": false,
"complements": [],
"uses_on_self_invoice_portal": ["G03", "S01"],
"invoice_pdf_notes": "Additional notes for PDF",
"product_key": "81112209",
"unit_key": "E48",
"use": "G03",
"automate_complement_for_ppd_invoices": true,
"withholding_taxes": [],
"customer_portal_id": "portal_1234567890",
"periodicity": {
"label": "Mes",
"value": "month"
},
"default_series": {
"income": {
"serie": "A"
},
"complements": {
"serie": "P"
},
"credit_note": {
"serie": "NC"
}
}
},
"tax_id": "EMP800101ABC",
"tax_system": "601",
"support_email": "support@empresa.com",
"support_phone": "+52 55 1234 5678",
"owner": "user_1234567890",
"created_at": 1677651234,
"credit_limit": 1000,
"used_credits": 250,
"credit_period_start": 1677651234000,
"sat": {
"completed": true,
"connected_at": 1677651234,
"csd_expires_at": 1924991999
},
"members": [
{
"id": "user_1234567890",
"email": "member@empresa.com",
"role": "admin"
}
],
"integrations": {
"stripe": {
"completed": false,
"category": "payments"
},
"mercadopago": {
"completed": false,
"category": "payments"
},
"clip": {
"completed": false,
"category": "payments"
},
"whmcs": {
"completed": false,
"category": "payments"
},
"paypal": {
"completed": false,
"category": "payments"
},
"openpay": {
"completed": false,
"category": "payments"
},
"conekta": {
"completed": false,
"category": "payments"
},
"bank": {
"completed": false,
"category": "payments"
},
"shopify": {
"completed": false,
"category": "payments"
},
"zapier": {
"completed": false,
"category": "payments"
},
"airtable": {
"completed": false,
"category": "payments"
},
"google_sheets": {
"completed": false,
"category": "payments"
},
"hilos": {
"completed": false,
"category": "messaging"
}
},
"metadata": {
"custom_field": "value"
}
}
],
"next": "team_dmU311Ajzj",
"total_results": 105,
"has_more": true
}
```
### Create Team
```http theme={null}
POST /teams
```
Create a new team with initial configuration.
> Requires the `multipleIssuerAccounts` feature on your plan. Without it this endpoint returns `403`, as does `DELETE /teams/{id}`.
**Request Body:**
```json theme={null}
{
"legal_name": "Empresa de Tecnología S.A. de C.V.",
"tax_id": "EMP800101ABC",
"tax_system": "601",
"brand": {
"alias": "My Company",
"primary_color": "#FF0000",
"secondary_color": "#00FF00",
"logo": "https://example.com/logo.png"
},
"support_email": "support@company.com",
"support_phone": "+52 55 1234 5678",
"generate_onboarding_url": true,
"address": {
"country": "MEX",
"street": "Av. Insurgentes Sur",
"exterior": "123",
"interior": "4B",
"neighborhood": "Del Valle",
"municipality": "Benito Juárez",
"city": "Ciudad de México",
"state": "CDMX",
"zip": "03100"
},
"metadata": {
"external_id": "erp-1234",
"segment": "enterprise"
},
"add_members": [
{
"id": "user123abc",
"role": "editor"
},
{
"id": "user456def",
"role": "viewer"
}
],
"add_master_team_members": false
}
```
**Body Parameters:**
* `legal_name` (string, optional) - Legal name of the team/company
* `tax_id` (string, optional) - Tax identification number (RFC)
* `tax_system` (string, optional) - SAT tax system code (regimen fiscal)
* `brand` (object, optional) - Branding configuration
* `alias` (string, required within object) - Team display name
* `primary_color` (string, optional) - Primary brand color
* `secondary_color` (string, optional) - Secondary brand color
* `logo` (string, optional) - Logo URL
* `support_email` (string, optional) - Support contact email
* `support_phone` (string, optional) - Support contact phone
* `generate_onboarding_url` (boolean, optional) - When true, includes a secure onboarding URL in the response
* `address` (object, optional) - Team address information
* `country` (string, required within object) - Country code (e.g., "MEX")
* `street` (string, optional) - Street address
* `exterior` (string, optional) - Exterior number
* `interior` (string, optional) - Interior number
* `neighborhood` (string, optional) - Neighborhood/colony
* `municipality` (string, optional) - Municipality
* `city` (string, optional) - City
* `state` (string, optional) - State/province
* `zip` (string, optional) - Postal code
* `metadata` (object, optional) - Arbitrary key-value pairs to store with the team
* `add_members` (array, optional) - Array of members to add to the team on creation
* `id` (string, required) - User ID to add as a team member
* `role` (string, optional, defaults to "viewer") - Member role. Options: "admin", "editor", "viewer"
* `add_master_team_members` (boolean, optional) - When true, copies all members from the master team to the newly created team with their existing permissions. Only applicable for gigstack Connect accounts
**Example Request (basic team creation):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"legal_name": "Tech Solutions S.A. de C.V.",
"brand": {
"alias": "Tech Solutions SA"
},
"tax_id": "TSO123456789",
"tax_system": "601"
}'
```
**Example Request (with members):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"legal_name": "Tech Solutions S.A. de C.V.",
"brand": {
"alias": "Tech Solutions SA"
},
"tax_id": "TSO123456789",
"tax_system": "601",
"add_members": [
{
"id": "user123abc",
"role": "editor"
},
{
"id": "user456def",
"role": "viewer"
}
]
}'
```
**Example Request (with master team members):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"legal_name": "Tech Solutions S.A. de C.V.",
"brand": {
"alias": "Tech Solutions SA"
},
"tax_id": "TSO123456789",
"tax_system": "601",
"add_master_team_members": true
}'
```
**Example Response (201 Created):**
```json theme={null}
{
"success": true,
"message": "Team created successfully",
"timestamp": 1768240724433,
"data": {
"id": "team_1234567890",
"legal_name": "Tech Solutions S.A. de C.V.",
"tax_id": "TSO123456789",
"tax_system": "601",
"brand": {
"alias": "Tech Solutions SA",
"primary_color": null,
"secondary_color": null,
"logo": null
},
"address": {
"country": "MEX",
"street": null,
"zip": null,
"city": null,
"state": null,
"exterior": null,
"interior": null,
"neighborhood": null
},
"support_email": null,
"support_phone": null,
"owner": "user_owner123",
"members": [
{
"id": "user_owner123",
"email": "owner@techsolutions.com",
"role": "admin"
}
],
"settings": {},
"sat": {
"completed": false,
"connected_at": null,
"csd_expires_at": null
},
"integrations": {
"stripe": { "completed": false, "category": "payments" },
"mercadopago": { "completed": false, "category": "payments" }
},
"metadata": null,
"created_at": 1768240724433,
"onboarding_url": ""
}
}
```
Note: `onboarding_url` is only populated when `generate_onboarding_url: true` is sent in the request. Otherwise it is an empty string.
### Get Team
```http theme={null}
GET /teams/{id}
```
Retrieve a specific team by ID.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/teams/team_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"message": "Team retrieved successfully",
"timestamp": "2026-01-12T18:00:01.845Z",
"data": {
"id": "team_1234567890",
"legal_name": "Empresa de Tecnología S.A. de C.V.",
"address": {
"country": "MEX",
"street": "Av. Insurgentes Sur 456",
"zip": "03100",
"city": "Ciudad de México",
"state": "CDMX",
"exterior": "96",
"interior": "10",
"neighborhood": "Polanco"
},
"brand": {
"alias": "Mi Empresa",
"primary_color": "#007bff",
"secondary_color": "#6c757d",
"logo": "https://example.com/logo.png"
},
"settings": {
"avoid_automations_on_currencies": ["USD", "EUR"],
"default_description": "Default invoice description",
"taxes": [],
"taxes_usd": [],
"emails": {
"invoices_bcc": ["accounting@example.com"],
"avoid_invoice_emails": false,
"avoid_test_invoice_emails": true,
"avoid_receipts_emails": false
},
"override_item_description": "Custom item description",
"global_invoice_disabled": false,
"complements": [],
"uses_on_self_invoice_portal": ["G03", "S01"],
"invoice_pdf_notes": "Additional notes for PDF",
"product_key": "81112209",
"unit_key": "E48",
"use": "G03",
"automate_complement_for_ppd_invoices": true,
"withholding_taxes": [],
"customer_portal_id": "portal_1234567890",
"periodicity": {
"label": "Mes",
"value": "month"
},
"default_series": {
"income": {
"serie": "A"
},
"complements": {
"serie": "P"
},
"credit_note": {
"serie": "NC"
}
}
},
"tax_id": "EMP800101ABC",
"tax_system": "601",
"support_email": "support@empresa.com",
"support_phone": "+52 55 1234 5678",
"owner": "user_1234567890",
"created_at": 1677651234,
"sat": {
"completed": true,
"connected_at": 1677651234,
"csd_expires_at": 1924991999
},
"members": [
{
"id": "user_1234567890",
"email": "member@empresa.com",
"role": "admin"
}
],
"integrations": {
"stripe": {
"completed": false,
"category": "payments"
},
"mercadopago": {
"completed": false,
"category": "payments"
},
"clip": {
"completed": false,
"category": "payments"
},
"whmcs": {
"completed": false,
"category": "payments"
},
"paypal": {
"completed": false,
"category": "payments"
},
"openpay": {
"completed": false,
"category": "payments"
},
"conekta": {
"completed": false,
"category": "payments"
},
"bank": {
"completed": false,
"category": "payments"
},
"shopify": {
"completed": false,
"category": "payments"
},
"zapier": {
"completed": false,
"category": "payments"
},
"airtable": {
"completed": false,
"category": "payments"
},
"google_sheets": {
"completed": false,
"category": "payments"
},
"hilos": {
"completed": false,
"category": "messaging"
}
},
"metadata": {
"custom_field": "value"
}
}
}
```
## Team Response Structure
### Core Fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique team identifier |
| `legal_name` | string | Official registered legal name of the company/team |
| `tax_id` | string | Tax identification number (RFC) |
| `tax_system` | string | SAT tax system code |
| `support_email` | string | Team support contact email |
| `support_phone` | string | Team support contact phone |
| `owner` | string | User ID of the team owner |
| `created_at` | number | Unix timestamp of team creation |
| `credit_limit` | number \| null | Maximum documents (credits) the team can create per billing period. Null means no per-team limit. |
| `used_credits` | number | Number of credits used by this team in the current billing period |
| `credit_period_start` | number \| null | Unix timestamp (ms) when the current credit period started |
| `metadata` | object | Custom metadata key-value pairs |
### Address Object
| Field | Type | Description |
| - | - | - |
| `country` | string | Country code (e.g., "MEX") |
| `street` | string | Street address |
| `zip` | string | Postal code |
| `city` | string | City name |
| `state` | string | State or province |
| `exterior` | string | Exterior number |
| `interior` | string | Interior number/apartment |
| `neighborhood` | string | Neighborhood or colony |
### Brand Object
| Field | Type | Description |
| - | - | - |
| `alias` | string | Team display name |
| `primary_color` | string | Primary brand color (hex) |
| `secondary_color` | string | Secondary brand color (hex) |
| `logo` | string | URL to brand logo image |
### Settings Object
The settings object contains comprehensive configuration for team operations:
| Field | Type | Description |
| - | - | - |
| `avoid_automations_on_currencies` | array | List of currency codes to skip automation |
| `default_description` | string | Default invoice item description |
| `taxes` | array | Default tax configuration for MXN |
| `taxes_usd` | array/boolean | Tax configuration for USD transactions |
| `emails` | object | Email notification settings |
| `override_item_description` | string | Override for all item descriptions |
| `global_invoice_disabled` | boolean | Whether global invoicing is disabled |
| `complements` | array | CFDI complements configuration |
| `uses_on_self_invoice_portal` | array | Allowed CFDI uses on self-invoice portal |
| `invoice_pdf_notes` | string | Additional notes for PDF invoices |
| `product_key` | string | Default SAT product key |
| `unit_key` | string | Default SAT unit key |
| `use` | string | Default CFDI use code |
| `automate_complement_for_ppd_invoices` | boolean | Auto-create payment complements for PPD invoices |
| `withholding_taxes` | array | Withholding tax configuration |
| `customer_portal_id` | string | Associated customer portal ID |
| `periodicity` | object | Default billing periodicity |
| `default_series` | object | Default invoice series configuration |
#### Email Settings
| Field | Type | Description |
| - | - | - |
| `invoices_bcc` | array | BCC email addresses for invoices |
| `avoid_invoice_emails` | boolean | Skip invoice email notifications |
| `avoid_test_invoice_emails` | boolean | Skip test invoice emails |
| `avoid_receipts_emails` | boolean | Skip receipt email notifications |
#### Periodicity Object
| Field | Type | Description |
| - | - | - |
| `label` | string | Display label (e.g., "Mes") |
| `value` | string | Value code (e.g., "month") |
#### Default Series Object
| Field | Type | Description |
| - | - | - |
| `income` | object | Income invoice series configuration |
| `complements` | object | Complement invoice series configuration |
| `credit_note` | object | Credit note series configuration |
Each series object contains:
* `serie` (string): Series identifier
### SAT Object
| Field | Type | Description |
| - | - | - |
| `completed` | boolean | Whether SAT configuration is complete |
| `connected_at` | number | Unix timestamp of SAT connection |
| `csd_expires_at` | number | Unix timestamp when CSD certificate expires |
### Members Array
Each member object contains:
| Field | Type | Description |
| - | - | - |
| `id` | string | User ID |
| `email` | string | User email address |
| `role` | string | Member role (admin, editor, viewer) |
### Integrations Object
The integrations object contains status for all available integrations. Each integration has:
| Field | Type | Description |
| - | - | - |
| `completed` | boolean | Whether integration is configured |
| `category` | string | Integration category (payments, messaging) |
**Available Integrations:**
* Payment providers: `stripe`, `mercadopago`, `clip`, `whmcs`, `paypal`, `openpay`, `conekta`, `bank`, `shopify`
* Data integrations: `zapier`, `airtable`, `google_sheets`
* Messaging: `hilos`
### Update Team
```http theme={null}
PUT /teams/{id}
```
Update team information. Uses the same schema as Create Team but all fields are optional for updates.
**Request Body (same as Create Team, all fields optional):**
```json theme={null}
{
"brand": {
"alias": "Tech Solutions International"
},
"support_email": "help@techsolutions.com",
"tax_system": "601"
}
```
**Example Request:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/teams/team_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"brand": {
"alias": "Tech Solutions International"
},
"support_email": "help@techsolutions.com"
}'
```
### Update Team Settings
```http theme={null}
PUT /teams/{id}/settings
```
Update comprehensive team settings including defaults, taxes, and automation.
> ### ⚠️ Here `periodicity` is `two_months` — plural
>
> `PUT /teams/{id}/settings` accepts exactly:
>
> ```
> day | week | two_weeks | month | two_months
> ```
>
> sent as a **plain string**, not an object. Anything else fails validation with `400`.
>
> **The receipts endpoints use `two_month` — singular** (`POST /v2/receipts`, field `periodicity`). The two endpoints genuinely disagree and neither accepts the other's spelling. This is the current state of the API, deliberately left alone because normalizing it would break live integrations. Never copy a periodicity value between the two — look it up. See [Receipts → Periodicity Options](/guides/receipts#periodicity-options).
>
> Note the asymmetry between write and read: you **send** `periodicity` as a string here, but `GET /teams/{id}` returns whatever is stored under the team's `defaults.periodicity`, which for teams configured through the dashboard is a `{ label, value }` object. Read `periodicity.value` defensively.
**Request Body:**
```json theme={null}
{
"avoid_legal_name_replacer": false,
"default_description": "Professional services",
"invoice_pdf_notes": "Thank you for your business",
"product_key": "80141503",
"unit_key": "E48",
"use": "P01",
"periodicity": "month",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"factor": "Tasa",
"withholding": false
}
],
"taxes_usd": [
{
"type": "IVA",
"rate": 0.0,
"factor": "Exento"
}
],
"emails": {
"invoices_bcc": ["accounting@company.com"],
"avoid_invoice_emails": false,
"avoid_test_invoice_emails": true,
"avoid_receipts_emails": false
},
"default_series": {
"income": {
"serie": "A",
"folio_number_live": 1001,
"folio_number_test": 1
},
"complements": {
"serie": "C",
"folio_number_live": 1001,
"folio_number_test": 1
},
"credit_note": {
"serie": "N",
"folio_number_live": 1001,
"folio_number_test": 1
}
},
"automate_complement_for_ppd_invoices": true,
"global_invoice_disabled": false,
"uses_on_self_invoice_portal": ["P01", "G03", "G01"]
}
```
**Example Request:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/teams/team_1234567890/settings \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"default_description": "Consulting services",
"product_key": "80141503",
"unit_key": "E48",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
],
"emails": {
"invoices_bcc": ["finance@company.com"],
"avoid_test_invoice_emails": true
}
}'
```
### Get Team Integrations
```http theme={null}
GET /teams/integrations
```
> **🚧 Not yet available.** The route is registered and reachable, but the handler is still a stub. Every call returns:
>
> ```http theme={null}
> HTTP/1.1 501 Not Implemented
> ```
>
> ```json theme={null}
> { "message": "Not implemented" }
> ```
>
> Do not build against it. **Use `GET /teams/{id}` instead** — the team response already carries the [Integrations Object](#integrations-object) with a `completed` flag per provider, which is the information this endpoint is eventually meant to serve.
>
> (Historical note: this path used to be unreachable altogether — it was registered after `GET /teams/{id}`, so Express matched `integrations` as a team id and you got a 404 for a team that does not exist. The ordering is fixed; only the handler remains.)
### Add Team Member
```http theme={null}
POST /teams/{id}/add-member
```
Add a member to a team with a specified role.
**Request Body:**
```json theme={null}
{
"id": "user_9876543210",
"role": "editor"
}
```
**Body Parameters:**
* `id` (string, required) - User ID of the member to add
* `role` (string, optional) - Member role. If not specified, defaults to "viewer"
* Options: `"admin"`, `"editor"`, `"viewer"`
* Default: `"viewer"`
**Note:** Body parameters are validated in the handler using `withBaseSchema()` with inline validation.
**Example Request (with specified role):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams/team_1234567890/add-member \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "user_9876543210",
"role": "editor"
}'
```
**Example Request (default role - viewer):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams/team_1234567890/add-member \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "user_9876543210"
}'
```
### Remove Team Member
```http theme={null}
POST /teams/{id}/remove-member
```
Remove a member from a team.
**Request Body:**
```json theme={null}
{
"id": "user_9876543210"
}
```
**Body Parameters:**
* `id` (string, required) - User ID of the member to remove
**Note:** Body parameters are validated in the handler using `withBaseSchema()` with inline validation.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams/team_1234567890/remove-member \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "user_9876543210"
}'
```
### Get Team Series
```http theme={null}
GET /teams/{id}/series
```
Get invoice series configuration for a team.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/teams/team_1234567890/series \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Create Team Series
```http theme={null}
POST /teams/{id}/series
```
Create a new invoice series for a team.
**Request Body:**
```json theme={null}
{
"series": "B",
"live": 1000,
"test": 1
}
```
**Body Parameters:**
* `series` (string, required) - Series identifier (alphanumeric, max 10 characters)
* `live` (number, optional) - Initial folio number for live mode (default: 0)
* `test` (number, optional) - Initial folio number for test mode (default: 0)
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams/team_1234567890/series \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"series": "B",
"live": 1000,
"test": 1
}'
```
### Update Team Series
```http theme={null}
PUT /teams/{id}/series/{seriesId}
```
Update an existing team series.
**Request Body:**
```json theme={null}
{
"live": 2000,
"test": 50
}
```
**Body Parameters:**
* `live` (number, optional) - Update folio number for live mode
* `test` (number, optional) - Update folio number for test mode
Note: At least one of `live` or `test` must be provided.
**Example Request:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/teams/team_1234567890/series/B \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"live": 2000
}'
```
### Get Team Onboarding URL
```http theme={null}
GET /teams/{id}/onboarding-url
```
Generate a secure onboarding URL for team setup and configuration. This endpoint is only available for gigstack Connect accounts (master teams).
**Important:** This endpoint is only available for gigstack Connect accounts.
**Use Cases:**
* Generate onboarding links for new teams
* Allow secure team configuration setup
* Enable embedded team management flows
**Parameters:**
* `id` (path, required) - Team ID to generate onboarding URL for
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/teams/team_1234567890/onboarding-url \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"data": "https://embeded.gigstack.pro/?sessionId=otpOnboarding_abc123&c=secure_token",
"message": "Onboarding URL generated successfully"
}
```
**Error Responses:**
* **401 Unauthorized** - Only available for master teams
* **404 Not Found** - Team not found
The generated URL provides secure access to the team onboarding interface with a temporary session and secure code.
### Upload SAT CSD Certificates
```http theme={null}
POST /teams/{id}/sat-connection
```
Upload SAT CSD (Certificado de Sello Digital) certificates to establish SAT connection for CFDI invoicing. This endpoint accepts multipart form data with the certificate files and password.
**Required Files:**
* **cert**: Certificate file (.cer) - The public certificate
* **key**: Private key file (.key) - The encrypted private key
* **keyPass**: Password for the private key
**First-Time Connection:**
When this is the first SAT connection for a team (no previous SAT setup), the system will automatically initialize default invoice series (G, NC, P, T).
**gigstack Connect:** Upload SAT certificates for other teams using the `team` query parameter.
**Parameters:**
* `id` (path, required) - Team ID to upload SAT certificates for
* `team` (query, optional) - Target team ID for gigstack Connect
**Request Body (multipart/form-data):**
* `cert` (binary, required) - Certificate file (.cer)
* `key` (binary, required) - Private key file (.key)
* `keyPass` (string, required) - Password for the private key
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams/team_1234567890/sat-connection \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "cert=@certificate.cer" \
-F "key=@private_key.key" \
-F "keyPass=your_certificate_password"
```
**Example Response:**
```json theme={null}
{
"message": "SAT connection established successfully",
"data": {
"isValid": true,
"details": {
"serialNumber": "30001000000500003416",
"validTo": 1735689600000
}
}
}
```
**Response Fields:**
* `isValid` (boolean) - Whether the certificate is valid
* `details.serialNumber` (string) - Certificate serial number
* `details.validTo` (number) - Unix timestamp in milliseconds when certificate expires
**Error Responses:**
**400 - Bad Request:**
```json theme={null}
{
"message": "Invalid certificate",
"error": "Missing required files or invalid certificate format"
}
```
**401 - Unauthorized:**
```json theme={null}
{
"message": "Unauthorized",
"error": "Invalid or missing authentication token"
}
```
**403 - Forbidden:**
```json theme={null}
{
"message": "Access denied",
"error": "Team not in same billing account"
}
```
### Sign Manifest Document
```http theme={null}
POST /teams/{id}/manifest/sign
```
Sign a manifest document (Carta Manifiesto) using the FIEL (Firma Electronica Avanzada) for SAT compliance. This endpoint is used to sign the authorization manifest that authorizes the PAC (Proveedor Autorizado de Certificacion) to issue CFDI invoices on behalf of your team's RFC.
The manifest must be signed to grant the PAC permission to stamp and process invoices under your team's tax identification. Once signed, the manifest is stored in your team's SAT configuration and includes both XML and PDF files.
**Important Requirements:**
* Your SAT configuration must be completed before signing the manifest
* The FIEL certificate must be valid and issued by SAT
* The certificate must match your team's RFC
* The team ID is specified in the URL path parameter
**Supported Formats:**
This endpoint accepts two content types:
1. **JSON format (application/json):** Send Base64 encoded certificate files
2. **Form Data format (multipart/form-data):** Upload certificate files directly
**Parameters:**
* `id` (path, required) - Team ID to sign manifest for
**Request Body (JSON):**
```json theme={null}
{
"key": "MIIFDjBABgkqhkiG9w0BBQ0wMz...",
"cert": "MIIFuzCCA6OgAwIBAgIUMzAwMD...",
"password": "my_secure_password"
}
```
**Request Body (Form Data):**
* `key` (file) - FIEL .key file upload
* `cert` (file) - FIEL .cer file upload
* `password` (string) - FIEL password (private key password)
**Example Request (JSON):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams/team_1234567890/manifest/sign \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key": "MIIFDjBABgkqhkiG9w0BBQ0wMz...",
"cert": "MIIFuzCCA6OgAwIBAgIUMzAwMD...",
"password": "my_secure_password"
}'
```
**Example Request (Form Data):**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/teams/team_1234567890/manifest/sign \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "key=@/path/to/fiel.key" \
-F "cert=@/path/to/fiel.cer" \
-F "password=my_secure_password"
```
**Example Response:**
```json theme={null}
{
"message": "Manifest signed successfully",
"data": {
"xmlBase64": "PD94bWwgdmVyc2lvbj0iMS4wIi...",
"pdfBase64": "JVBERi0xLjQKJeLjz9MKMyAwIG...",
"fechaFirma": "2024-01-08T15:30:00.000Z",
"mensajeResultado": "Firma exitosa"
}
}
```
**Response Fields:**
* `xmlBase64` (string) - Base64 encoded signed manifest XML
* `pdfBase64` (string) - Base64 encoded manifest PDF
* `fechaFirma` (string) - Signature date and time in ISO 8601 format
* `mensajeResultado` (string) - Result message from signing service
**Error Responses:**
| Status | Error | Description |
| - | - | - |
| 400 | key (Base64 encoded .key file) is required | Missing required key field |
| 400 | cert (Base64 encoded .cert file) is required | Missing required cert field |
| 400 | password is required | Missing required password field |
| 400 | Certificado FIEL invalido | Invalid FIEL certificate |
| 400 | El RFC del certificado no coincide con el RFC del equipo | Certificate RFC does not match team RFC |
| 400 | SAT configuration is incomplete | SAT setup must be completed before signing manifests |
| 400 | Manifest signing failed: \[error message] | Signing service rejected the request |
| 401 | Unauthorized | Invalid or missing authentication token |
| 404 | Team not found | Team does not exist |
## Team Settings Structure
### Core Settings
| Setting | Type | Description |
| - | - | - |
| `default_description` | string | Default item description |
| `product_key` | string | Default SAT product key |
| `unit_key` | string | Default SAT unit key |
| `use` | string | Default CFDI use code |
| `periodicity` | object | Default billing/invoicing period with label and value |
| `invoice_pdf_notes` | string | Notes added to PDF invoices |
| `override_item_description` | string | Override all item descriptions |
| `avoid_automations_on_currencies` | array | List of currency codes to skip automation |
| `global_invoice_disabled` | boolean | Whether global invoicing is disabled |
| `automate_complement_for_ppd_invoices` | boolean | Auto-create payment complements for PPD invoices |
| `customer_portal_id` | string | Associated customer portal ID |
| `uses_on_self_invoice_portal` | array | Allowed CFDI uses on self-invoice portal |
| `complements` | array | CFDI complements configuration |
| `default_series` | object | Default invoice series for income, complements, and credit notes |
### Tax Configuration
```json theme={null}
{
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"factor": "Tasa",
"withholding": false
}
],
"taxes_usd": [
{
"type": "IVA",
"rate": 0.0,
"factor": "Exento"
}
],
"withholding_taxes": [
{
"type": "ISR",
"rate": 0.1,
"withholding": true
}
]
}
```
### Email Settings
```json theme={null}
{
"emails": {
"invoices_bcc": ["accounting@company.com", "admin@company.com"],
"avoid_invoice_emails": false,
"avoid_test_invoice_emails": true,
"avoid_receipts_emails": false
}
}
```
### Series Configuration
```json theme={null}
{
"default_series": {
"income": {
"serie": "A",
"folio_number_live": 1001,
"folio_number_test": 1
},
"complements": {
"serie": "C",
"folio_number_live": 1001,
"folio_number_test": 1
},
"credit_note": {
"serie": "N",
"folio_number_live": 1001,
"folio_number_test": 1
}
}
}
```
## Configuration Examples
### Basic Team Setup
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/teams/team_1234567890/settings \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"product_key": "80141503",
"unit_key": "E48",
"use": "P01",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
]
}'
```
### Professional Services Configuration
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/teams/team_1234567890/settings \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"default_description": "Professional consulting services",
"product_key": "80141503",
"unit_key": "HUR",
"taxes": [
{
"type": "IVA",
"rate": 0.16,
"withholding": false
},
{
"type": "ISR",
"rate": 0.10,
"withholding": true
},
{
"type": "IVA",
"rate": 0.106667,
"withholding": true
}
],
"automate_complement_for_ppd_invoices": true
}'
```
### E-commerce Configuration
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/teams/team_1234567890/settings \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"default_description": "Online purchase",
"product_key": "01010101",
"unit_key": "H87",
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
],
"uses_on_self_invoice_portal": ["G01", "G03"],
"emails": {
"invoices_bcc": ["ventas@tienda.com"],
"avoid_test_invoice_emails": true
}
}'
```
### International Business Configuration
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/teams/team_1234567890/settings \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"taxes": [
{
"type": "IVA",
"rate": 0.16
}
],
"taxes_usd": [
{
"type": "IVA",
"rate": 0.00,
"factor": "Exento"
}
],
"complements": [
{
"type": "export",
"enabled": true
}
]
}'
```
## Best Practices
1. **Configure defaults early** - Set up team settings before creating invoices
2. **Use appropriate tax settings** - Configure taxes based on business type
3. **Set up email BCCs** - Ensure accounting gets copies
4. **Manage series carefully** - Don't duplicate series across invoice types
5. **Test in staging** - Verify settings with test folios first
6. **Enable automations wisely** - Understand impact on workflows
7. **Keep member roles updated** - Regular access reviews
## Member Roles
| Role | Permissions |
| - | - |
| **admin** | Full access |
| **editor** | Create/edit resources |
| **viewer** | Read-only access |
## Team Workflows
### Initial Setup
```mermaid theme={null}
graph LR
A[Create Team] --> B[Configure Settings]
B --> C[Add Members]
C --> D[Set Up Series]
D --> E[Configure Integrations]
```
### Member Management
```mermaid theme={null}
graph LR
A[Invite User] --> B[Assign Role]
B --> C[User Accepts]
C --> D[Access Granted]
```
## Related Resources
* [Users API](/guides/users) - Manage team members
* [gigstack Connect](/guides/gigstack-connect) - Multi-team access
* [Invoices API](/guides/invoices) - Uses team settings
* [Payments API](/guides/payments) - Uses team configuration
## Error Handling
### Team Not Found
```json theme={null}
{
"message": "Team not found",
"error": "The specified team does not exist"
}
```
### Invalid Tax Configuration
```json theme={null}
{
"message": "Invalid tax settings",
"error": "Tax rate must be between 0 and 1"
}
```
### Member Already Exists
```json theme={null}
{
"message": "Member already in team",
"error": "User is already a member of this team"
}
```
### Invalid Series
```json theme={null}
{
"message": "Series conflict",
"error": "Series 'A' already exists for income invoices"
}
```
### Permission Denied
```json theme={null}
{
"message": "Insufficient permissions",
"error": "Admin role required for this action"
}
```
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Users API Guide
Source: https://docs.gigstack.io/guides/users
Integration guide for Users
Some operations in this guide have Discovery handlers but no public gateway route yet: `POST /users/reset-password/{id}`. Check the availability notice on each API reference page before using them.
Manage user accounts, profiles, and access control. The Users API handles user creation, updates, password management, and team associations.
## Overview
Users represent individual accounts that can access teams and resources. Each user has a profile, role assignments, and can belong to multiple teams with different permission levels.
## Key Features
* **User Management** - Create and update user accounts
* **Profile Information** - Manage user details and contact info
* **Password Reset** - Secure password management
* **Login Link Generation** - Create direct login links for API-created users
* **Role Assignment** - Control access levels per team
* **Multi-team Support** - Users can belong to multiple teams
* **Address Management** - Store user location information
## Endpoints
### List Users
```http theme={null}
GET /users
```
Retrieve a paginated list of users.
**Query Parameters:**
* `limit` (integer, 1-100) - Number of results per page (default: 10)
* `next` (string) - Pagination cursor for next page
* `team` (string) - gigstack Connect: Target team ID
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/users?limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "Users retrieved successfully",
"data": [
{
"id": "user_1234567890",
"email": "john.doe@example.com",
"name": "John Doe",
"role": "admin",
"created_at": 1677651234
},
{
"id": "user_9876543210",
"email": "jane.smith@example.com",
"name": "Jane Smith",
"role": "member",
"created_at": 1677651234
}
],
"has_more": false,
"total_results": 2
}
```
### Create User
```http theme={null}
POST /users
```
Create a new user account.
**Request Body:**
```json theme={null}
{
"email": "new.user@example.com",
"first_name": "New",
"last_name": "User",
"phone": "+52 55 1234 5678",
"company_role": "Developer",
"address": {
"country": "MEX",
"street": "Av. Reforma",
"zip": "06500",
"city": "Ciudad de México",
"state": "CDMX",
"exterior": "456",
"neighborhood": "Juárez"
},
"auto_join": true,
"role": "editor"
}
```
**Body Parameters:**
* `email` (string, optional) - User email address
* `first_name` (string, optional) - User first name
* `last_name` (string, optional) - User last name
* `phone` (string, optional) - User phone number
* `company_role` (string, optional) - User role in the company
* `address` (object, optional) - User address information
* `country` (string, optional) - Country name
* `street` (string, optional) - Street address
* `zip` (string, optional) - Postal code
* `city` (string, optional) - City name
* `state` (string, optional) - State/province
* `exterior` (string, optional) - Exterior number
* `municipality` (string, optional) - Municipality (stored but not returned in responses)
* `neighborhood` (string, optional) - Neighborhood/colony
* `auto_join` (boolean, optional) - If `true`, automatically adds the user to the team associated with the API key. Defaults to `false`. When `true` and `role` is specified, the user will be added to the team with that role.
* `role` (string, optional) - Role to assign to the user when `auto_join` is `true`. Can be `"editor"`, `"admin"`, or `"viewer"`. Defaults to `"viewer"` if not specified. This parameter only takes effect when `auto_join` is `true` - it determines the permissions the user will have within the team.
**Notes:**
* The `municipality` field can be included in requests and will be stored, but is not returned in response objects.
* When `auto_join` is `true`, the user will be automatically added to the team and billing account associated with your API key.
* The `role` parameter works in conjunction with `auto_join`. When both are used, the user is added to the team with the specified role (editor, admin, or viewer). If `role` is not specified, the user defaults to "viewer" role.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/users \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "developer@company.com",
"first_name": "Carlos",
"last_name": "Rodriguez",
"phone": "+52 55 9876 5432",
"company_role": "Senior Developer",
"auto_join": true,
"role": "editor"
}'
```
**Example Response:**
```json theme={null}
{
"message": "User created successfully",
"data": {
"id": "user_new123456",
"email": "developer@company.com",
"name": "Carlos Rodriguez",
"role": "member",
"created_at": 1677651234,
"invitation_sent": true
}
}
```
### Get User
```http theme={null}
GET /users/{id}
```
Retrieve a specific user by ID.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/users/user_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "User retrieved successfully",
"data": {
"id": "user_1234567890",
"email": "john.doe@example.com",
"name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"phone": "+52 55 1234 5678",
"company_role": "Manager",
"role": "admin",
"address": {
"country": "MEX",
"street": "Av. Insurgentes Sur",
"zip": "03100",
"city": "Ciudad de México",
"state": "CDMX",
"exterior": "123",
"neighborhood": "Del Valle"
},
"created_at": 1677651234,
"last_login": 1677737634,
"teams": [
{
"team_id": "team_1234567890",
"team_name": "Main Company",
"role": "admin"
},
{
"team_id": "team_0987654321",
"team_name": "Subsidiary",
"role": "viewer"
}
]
}
}
```
### Update User
```http theme={null}
PUT /users/{id}
```
Update an existing user's information.
**Request Body:**
All fields are optional. Only provide the fields you want to update.
```json theme={null}
{
"first_name": "Jonathan",
"last_name": "Doe",
"phone": "+52 55 5555 5555",
"company_role": "Senior Manager",
"address": {
"zip": "03200",
"neighborhood": "Del Valle Sur"
}
}
```
**Example Request:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/users/user_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"phone": "+52 55 9999 9999",
"company_role": "Director"
}'
```
### Reset Password
```http theme={null}
POST /users/reset-password/{id}
```
Sends a password-reset email to the user. **The user is identified by the path parameter — there is no request body**, and passing an `email` in the body has no effect. The address used is the one on the user's auth record, not anything you supply.
The reset link in the email is valid for 24 hours.
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/users/reset-password/user_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"message": "User password reset successfully"
}
```
### Generate Login Link
```http theme={null}
POST /users/login-link
```
Generate a login link for a user. The link contains a custom Firebase token that allows the user to authenticate directly without a password.
**Requirements:**
* User must have been created via API (from: 'api')
* User must belong to the billing account making the request
* If requirements are not met, returns a 404 error
**gigstack Connect:** Generate login links for other teams' users using the `team` query parameter.
**Request Body:**
```json theme={null}
{
"user_id": "abc123xyz"
}
```
**Parameters:**
* `user_id` (required, string) - The Firebase UID of the user
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/users/login-link \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"user_id": "abc123xyz"
}'
```
**Example Response:**
```json theme={null}
{
"message": "Login link generated",
"data": {
"login_link": "https://app.gigstack.pro/auth/token-login?token=eyJhbGc...",
"valid_until": 1732657200000,
"method": "token"
}
}
```
**Response Fields:**
* `login_link` (string) - The login URL with embedded token
* `valid_until` (number) - Unix timestamp in milliseconds when the token expires (1 hour from generation)
* `method` (string) - The authentication method used (always 'token')
**Error Responses:**
**404 - User Not Found or Not Accessible:**
```json theme={null}
{
"message": "User not found or not accessible via API",
"error": "The user either does not exist or was not created via API"
}
```
## User Structure
### User Fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique user identifier |
| `email` | string | User's email address (unique) |
| `first_name` | string | User's first name |
| `last_name` | string | User's last name |
| `name` | string | Full name (computed) |
| `phone` | string | Contact phone number |
| `company_role` | string | Position in company |
| `role` | string | System role (admin/member/viewer) |
| `address` | object | User's address information |
| `created_at` | number | Unix timestamp of creation |
### Address Structure
The address object supports the following fields:
```json theme={null}
{
"country": "MEX",
"street": "Street name",
"zip": "12345",
"city": "City",
"state": "State",
"exterior": "123",
"neighborhood": "Neighborhood"
}
```
**Note:**
* The `municipality` field can be included in POST/PUT requests and will be stored in the database, but is not returned in GET responses.
* All address fields are optional and nullable.
* Address fields returned in responses: `country`, `street`, `zip`, `city`, `state`, `exterior`, `neighborhood`
## User Roles and Permissions
### System Roles
| Role | Permissions | Description |
| - | - | - |
| **admin** | Full access | Can manage team and billing |
| **member** | Read/Write | Can create and edit resources |
| **viewer** | Read only | Can only view resources |
### Role Hierarchy
```mermaid theme={null}
graph TD
A[Admin] --> B[Member]
B --> C[Viewer]
```
### Permission Matrix
| Action | Admin | Member | Viewer |
| - | - | - | - |
| View resources | Yes | Yes | Yes |
| Create invoices | Yes | Yes | No |
| Edit clients | Yes | Yes | No |
| Manage users | Yes | No | No |
| Change settings | Yes | No | No |
| Delete team | Yes | No | No |
| Manage billing | Yes | No | No |
## Common Scenarios
### 1. Onboard New Employee
**Option A: Using auto\_join with role (Recommended)**
```bash theme={null}
# Create user and automatically add to team with editor role
curl -X POST https://api.gigstack.io/v2/users \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "newemployee@company.com",
"first_name": "Maria",
"last_name": "Garcia",
"phone": "+52 55 1111 2222",
"company_role": "Accountant",
"auto_join": true,
"role": "editor"
}'
```
**Option B: Manual team addition**
```bash theme={null}
# Create user
curl -X POST https://api.gigstack.io/v2/users \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "newemployee@company.com",
"first_name": "Maria",
"last_name": "Garcia",
"phone": "+52 55 1111 2222",
"company_role": "Accountant"
}'
# Add to team (using Teams API)
curl -X POST https://api.gigstack.io/v2/teams/team_123/add-member \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"user_id": "user_new123",
"role": "member"
}'
```
### 2. Update User Profile
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/users/user_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Maria",
"last_name": "Garcia Lopez",
"phone": "+52 55 3333 4444",
"company_role": "Senior Accountant",
"address": {
"country": "MEX",
"city": "Guadalajara",
"state": "Jalisco",
"zip": "44100"
}
}'
```
### 3. Bulk User Creation with Different Roles
```javascript theme={null}
// Example: Create multiple users with different roles
const users = [
{
email: 'admin@company.com',
first_name: 'Admin',
last_name: 'User',
company_role: 'Manager',
auto_join: true,
role: 'admin',
},
{
email: 'developer@company.com',
first_name: 'Developer',
last_name: 'User',
company_role: 'Developer',
auto_join: true,
role: 'editor',
},
{
email: 'auditor@company.com',
first_name: 'Auditor',
last_name: 'User',
company_role: 'External Auditor',
auto_join: true,
role: 'viewer',
},
]
for (const user of users) {
await fetch('https://api.gigstack.io/v2/users', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify(user),
})
}
```
### 4. Password Reset Flow
```bash theme={null}
# Request password reset (user id in the path, no body)
curl -X POST https://api.gigstack.io/v2/users/reset-password/user_1234567890 \
-H "Authorization: Bearer YOUR_TOKEN"
# User receives email with reset link
# User clicks link and sets new password (handled by web app)
```
### 5. Generate Login Link for API User
```bash theme={null}
# Create user via API
USER_RESPONSE=$(curl -X POST https://api.gigstack.io/v2/users \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "apiuser@example.com",
"first_name": "API",
"last_name": "User",
"company_role": "External User"
}')
# Extract user ID from response
USER_ID=$(echo $USER_RESPONSE | jq -r '.data.id')
# Generate login link
curl -X POST https://api.gigstack.io/v2/users/login-link \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"user_id\": \"$USER_ID\"
}"
# Response includes login link valid for 1 hour
# Send this link to the user via email, SMS, etc.
```
**Use Cases:**
* Onboard external users without requiring password setup
* Provide temporary access to contractors or auditors
* Enable single sign-on for integrated applications
* Create magic links for embedded user experiences
## User Lifecycle
### User Creation Flow
```mermaid theme={null}
graph LR
A[Create User] --> B[Send Invitation]
B --> C[User Accepts]
C --> D[Set Password]
D --> E[First Login]
E --> F[Active User]
```
### User Deactivation Flow
```mermaid theme={null}
graph LR
A[Active User] --> B[Remove from Teams]
B --> C[Disable Access]
C --> D[Archive Data]
D --> E[Deactivated]
```
## Best Practices
1. **Use strong passwords** - Enforce password complexity requirements
2. **Regular access reviews** - Audit user permissions periodically
3. **Minimize admin users** - Only essential personnel should have admin access
4. **Complete profiles** - Ensure all user information is up-to-date
5. **Use appropriate roles** - Follow principle of least privilege
6. **Monitor user activity** - Track login and action patterns
7. **Clean up inactive users** - Remove or disable unused accounts
## Security Considerations
### Password Requirements
* Minimum 8 characters
* Must contain uppercase and lowercase
* Must contain numbers
* Must contain special characters
* Cannot reuse last 5 passwords
### Session Management
* Sessions expire after 24 hours of inactivity
* Refresh tokens valid for 30 days
* Multi-device support with individual sessions
## Related Resources
* [Teams API](/guides/teams) - Manage team membership
* [gigstack Connect](/guides/gigstack-connect) - Multi-team user access
* [Clients API](/guides/clients) - Users create and manage clients
* [Invoices API](/guides/invoices) - User permissions affect invoice access
## Error Handling
### User Already Exists
```json theme={null}
{
"message": "User creation failed",
"error": "Email address already registered"
}
```
### Invalid Email Format
```json theme={null}
{
"message": "Invalid request",
"error": "Email format is invalid"
}
```
### User Not Found
```json theme={null}
{
"message": "User not found",
"error": "The specified user does not exist"
}
```
### Insufficient Permissions
```json theme={null}
{
"message": "Access denied",
"error": "Admin role required to manage users"
}
```
### Password Reset Failed
```json theme={null}
{
"message": "Password reset failed",
"error": "No user found with that email address"
}
```
### Login Link Generation Failed
**User Not Created via API:**
```json theme={null}
{
"message": "User not found or not accessible via API",
"error": "This user was not created through the API or does not exist"
}
```
**User Not in Billing Account:**
```json theme={null}
{
"message": "Access denied",
"error": "User does not belong to your billing account"
}
```
***
For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)
# Webhooks API Guide
Source: https://docs.gigstack.io/guides/webhooks
Integration guide for Webhooks
Listen to real-time events in your gigstack account. The Webhooks API allows you to configure endpoints that receive event notifications when specific actions occur in your system.
## Overview
Webhooks enable you to build event-driven integrations by receiving automatic HTTP POST notifications when events occur in your gigstack account. Configure which events to listen for and where to receive them.
## Key Features
* **Real-time Notifications** - Instant event delivery to your endpoints
* **Event Filtering** - Subscribe only to events you need
* **Status Control** - Enable/disable webhooks without deletion
* **Multi-event Support** - Single webhook can listen to multiple event types
* **Automatic Retries** - Failed deliveries of resource events are retried with backoff (see [Delivery Behavior](#delivery-behavior))
## Endpoints
### List Webhooks
```http theme={null}
GET /webhooks
```
Retrieve all configured webhooks for your team.
**Query Parameters:**
* `limit` (integer, 1-100) - Number of results to return (default: 10)
* `status` (string) - Filter by status: `active` or `inactive`
* `team` (string) - gigstack Connect: Target team ID
**Example Request:**
```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/webhooks?limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"message": "Webhooks retrieved successfully",
"data": [
{
"id": "wh_dyS2ZVTj",
"url": "https://webhook.site/7cd05529-40fe-4c98-88e5-761de9c5feb1",
"events": [
"payment.created",
"payment.succeeded",
"invoice.created"
],
"status": "active",
"description": "Production payment notifications",
"owner": "8UWdgXELUhf022vuoq249mtGytG2",
"created_at": 1709090576567
}
],
"timestamp": 1709090576567
}
```
### Get Webhook
```http theme={null}
GET /webhooks/{id}
```
Retrieve details of a specific webhook.
**Example Request:**
```bash theme={null}
curl -X GET https://api.gigstack.io/v2/webhooks/wh_dyS2ZVTj \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"message": "Webhook retrieved successfully",
"data": {
"id": "wh_dyS2ZVTj",
"url": "https://webhook.site/7cd05529-40fe-4c98-88e5-761de9c5feb1",
"events": [
"payment.created",
"payment.succeeded",
"invoice.created"
],
"status": "active",
"description": "Production payment notifications",
"owner": "8UWdgXELUhf022vuoq249mtGytG2",
"created_at": 1709090576567
},
"timestamp": 1709090576567
}
```
### Create Webhook
```http theme={null}
POST /webhooks
```
Create a new webhook endpoint to receive event notifications.
**Request Body:**
```json theme={null}
{
"url": "https://your-domain.com/webhooks/gigstack",
"events": [
"payment.created",
"payment.succeeded",
"invoice.created"
],
"description": "Production webhook for payment events",
"status": "active"
}
```
**Required Fields:**
* `url` (string) - Endpoint URL that receives the events. Any valid URL is accepted; use HTTPS in production
* `events` (array) - List of event types to subscribe to (see Available Events below)
**Optional Fields:**
* `description` (string) - Human-readable description of the webhook
* `status` (string) - `active` or `inactive` (default: `active`)
**Example Request:**
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/webhooks \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhooks/gigstack",
"events": ["payment.created", "payment.succeeded"],
"description": "Payment notifications webhook"
}'
```
**Example Response:**
```json theme={null}
{
"success": true,
"message": "Webhook created successfully. Save the secret — it will not be shown again.",
"data": {
"id": "wh_dyS2ZVTj",
"url": "https://your-domain.com/webhooks/gigstack",
"events": ["payment.created", "payment.succeeded"],
"status": "active",
"description": "Payment notifications webhook",
"owner": "8UWdgXELUhf022vuoq249mtGytG2",
"created_at": 1709090576567,
"secret": "3f9a1c0e7b2d4f6a8c1e3b5d7f9a2c4e6b8d0f1a3c5e7b9d2f4a6c8e0b1d3f5a"
},
"timestamp": 1709090576600
}
```
> **Store `secret` now.** It is returned only in this response — `GET`, `PUT` and list calls never include it, and it cannot be revealed or rotated later. It signs `sat.invoice.synced` deliveries only (see [Verifying Signatures](#verifying-signatures)); resource events such as `payment.succeeded` are not signed with it. If you lose it, delete the webhook and create a new one.
### Update Webhook
```http theme={null}
PUT /webhooks/{id}
```
Update an existing webhook's configuration.
**Request Body:**
All fields are optional. Only include fields you want to update.
```json theme={null}
{
"url": "https://new-domain.com/webhooks/gigstack",
"events": ["payment.created", "payment.succeeded", "invoice.created"],
"description": "Updated webhook description",
"status": "inactive"
}
```
**Example Request:**
```bash theme={null}
curl -X PUT https://api.gigstack.io/v2/webhooks/wh_dyS2ZVTj \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "inactive",
"description": "Temporarily disabled for maintenance"
}'
```
### Delete Webhook
```http theme={null}
DELETE /webhooks/{id}
```
Permanently delete a webhook endpoint.
**Example Request:**
```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/webhooks/wh_dyS2ZVTj \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Example Response:**
```json theme={null}
{
"success": true,
"message": "Webhook deleted successfully"
}
```
## Available Events
A webhook can subscribe to any of these event types:
| Event | Meaning |
| - | - |
| `payment.created` | New payment request created |
| `payment.updated` | Payment information updated |
| `payment.succeeded` | Payment successfully processed |
| `payment.canceled` | Payment canceled |
| `payment.deleted` | Payment deleted |
| `payment.upcoming_due_date` | Payment due date approaching |
| `invoice.created` | New invoice created |
| `invoice.canceled` | Invoice canceled with SAT |
| `invoice.failed` | Invoice stamping failed |
| `invoice_batch.completed` | Every invoice of a [batch](/guides/invoice-batches) (`POST /invoices/income/batch`) has a final status |
| `receipt.created` | New receipt generated |
| `receipt.updated` | Receipt information updated |
| `receipt.completed` | Receipt stamped successfully |
| `receipt.deleted` | Receipt deleted |
| `customer.created` | New customer/client created |
| `customer.updated` | Customer information updated |
| `customer.deleted` | Customer deleted |
| `service.created` | New service added to catalog |
| `service.updated` | Service information updated |
| `service.deleted` | Service removed from catalog |
| `sat.invoice.synced` | The XML of an invoice downloaded from the SAT ([Descarga Masiva](/guides/descarga-masiva)) was fetched and stored |
> **Two kinds of delivery.**
>
> * **Resource events** (`payment.*`, `invoice.*`, `receipt.*`, `customer.*`, `service.*`) use the body for the webhook's [payload version](#payload-versions). They are retried and are not signed.
> * **`sat.invoice.synced`** always uses its [own body](#sat-sync-event-body). It is signed with the webhook's `secret` and is sent only once.
> * **`invoice_batch.completed`** always uses its [own body](#invoice-batch-event-body), and is delivered like `sat.invoice.synced`: signed and sent only once. Despite the prefix, it is not an `invoice.*` resource event.
## Webhook Structure
### Webhook Object
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique webhook identifier (prefix: `wh_`) |
| `url` | string | Endpoint URL that receives deliveries |
| `events` | array | List of subscribed event types |
| `status` | string | `active` or `inactive`. Only active webhooks receive deliveries |
| `description` | string | Optional description |
| `owner` | string | User ID who created the webhook |
| `created_at` | integer | Unix timestamp (milliseconds) of creation |
| `secret` | string | Signs `sat.invoice.synced` deliveries. **Returned only by `POST /webhooks`**, never again |
## Webhook Payload
Each delivery is an HTTP `POST` to your URL with `Content-Type: application/json`. Respond with any `2xx` status within 10 seconds.
### Payload versions
Each webhook sends resource events in one of two body formats:
| Version | Which webhooks | Body |
| - | - | - |
| `v1` | Created with `POST /webhooks`, unless your team's default is `v2` | [v1 body](#v1-body) |
| `v2` | Created or last saved in the gigstack dashboard, or on a team whose default is `v2` | [v2 body](#v2-body) |
The webhook object returned by the API does not include its version, but you can tell the two apart by shape:
* A `v2` body has `type` and `data.object`.
* A `v1` body has `event`, `team` and `webhook`.
Saving a webhook in the dashboard switches it to `v2`.
### v1 body
```json theme={null}
{
"event": "payment.succeeded",
"team": "team_1234567890",
"webhook": "wh_dyS2ZVTj",
"livemode": true,
"data": {
"...": "the resource as stored by gigstack when the event happened"
}
}
```
| Field | Type | Description |
| - | - | - |
| `event` | string | Event type |
| `team` | string | Team that owns the webhook |
| `webhook` | string | ID of the webhook receiving this delivery |
| `livemode` | boolean | `false` for test-mode resources |
| `data` | object | The resource at the moment of the event, in gigstack's internal (camelCase) format. `metadata` values are sent as strings |
| `connectedTeam` | string | Only on deliveries to a [gigstack Connect](/guides/gigstack-connect) master team: the connected team the event came from |
| `connectedTeamMetadata` | object | Only with `connectedTeam`, when that team has metadata. Values are strings |
A `v1` body has no event id. To de-duplicate, key on `event` plus `data.id`.
Teams on the legacy `v1.1` default receive the same object wrapped as `{ "payload": { … } }`. For `invoice.created`, their `data` also includes a `customer` object with the full address, and `date` is shifted by −6 hours.
### v2 body
```json theme={null}
{
"id": "log_4GqT7mZx9LpR2vWc8NdK--wh_dyS2ZVTj",
"type": "payment.succeeded",
"created": 1767225600000,
"livemode": true,
"data": {
"object": {
"...": "the resource in snake_case, as the API returns it"
}
}
}
```
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique per event and webhook. Stays the same across every retry and resend of that delivery, so use it to de-duplicate |
| `type` | string | Event type |
| `created` | integer | When the delivery was queued, Unix epoch **milliseconds** |
| `livemode` | boolean | `false` for test-mode resources |
| `data.object` | object | The resource in the same format the API returns. It is read **when the delivery is sent**, so a retried delivery can show a newer state than the event. For `*.deleted` events it is the last known state |
### Headers
| Header | Sent on |
| - | - |
| `Content-Type` | Every delivery: `application/json` |
| Custom headers | Resource events, when you add headers to the webhook in the gigstack dashboard (for example `Authorization`) |
| `X-Gigstack-Event` | `sat.invoice.synced` and `invoice_batch.completed` only: the event type |
| `X-Gigstack-Signature` | `sat.invoice.synced` and `invoice_batch.completed` only: `sha256=` + hex HMAC-SHA256 of the **raw body**, keyed with the webhook's `secret` |
Resource events are not signed. To authenticate them:
1. Add a secret header to the webhook in the dashboard, for example `Authorization: Bearer `.
2. Reject any request that doesn't carry it.
Custom headers can't be set through the API.
Webhooks created before signing was introduced have no `secret`, and their `sat.invoice.synced` and `invoice_batch.completed` deliveries have no `X-Gigstack-Signature` header. Recreate them to get one.
### SAT sync event body
`sat.invoice.synced` is sent by the SAT download service. Its body is the same regardless of the webhook's payload version:
```json theme={null}
{
"id": "evt_4f1c9a7e2b3d5c6a",
"event": "sat.invoice.synced",
"created_at": 1767225600,
"data": {
"uuid": "9D9B0E5B-0341-4C2B-8F3A-6E1D2C4B5A70",
"direction": "received",
"resource_status": "ready",
"issuer": { "rfc": "EKU9003173C9", "name": "ESCUELA KEMPER URGATE" },
"receiver": { "rfc": "MEE200101ABC", "name": "MI EMPRESA EJEMPLO" },
"total": 1160,
"currency": "MXN",
"issue_date": "2026-01-15T10:30:00",
"invoice_type": "I",
"status": "Vigente",
"team": "team_1234567890",
"credit_charged": true
}
}
```
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique event id: `evt_` + 16 hex characters. Use it to de-duplicate |
| `event` | string | Always `sat.invoice.synced` |
| `created_at` | integer | When the event was dispatched, Unix epoch **seconds** |
| `data` | object | The synced CFDI, described below |
Compared with resource events, the type is in `event`, the time is `created_at` in **seconds**, and there is no `livemode`.
#### `data` fields
| Field | Type | Description |
| - | - | - |
| `uuid` | string | Folio fiscal (UUID) of the CFDI |
| `direction` | string | `issued` or `received` |
| `resource_status` | string | Always `ready` — the XML is stored |
| `issuer` | object | Issuer of the CFDI |
| `receiver` | object | Receiver of the CFDI |
| `total` | number | CFDI total |
| `currency` | string | CFDI currency |
| `issue_date` | string | CFDI issue date |
| `invoice_type` | string | `I`, `E`, `P`, `N` or `T` |
| `status` | string | SAT status, e.g. `Vigente` |
| `team` | string | Team that owns the invoice |
| `credit_charged` | boolean | Whether a Descarga Masiva credit was charged for this XML |
| `retried` | boolean | Present and `true` when triggered by `POST /invoices/sat/{uuid}/retry-xml` |
### Invoice batch event body
`invoice_batch.completed` is sent once, when every accepted invoice of an [income invoice batch](/guides/invoice-batches) has a final status. Like the SAT event, its body is the same regardless of the webhook's payload version, the type is in `event` and `created_at` is in **seconds**:
```json theme={null}
{
"id": "evt_9a2b7c4d1e6f3a8b",
"event": "invoice_batch.completed",
"created_at": 1790784000,
"data": {
"id": "ibatch_5d41402abc4b2a76b9719d911017c592",
"livemode": true,
"total": 250,
"accepted": 248,
"rejected": 2,
"counts": { "queued": 0, "stamped": 245, "failed": 2, "duplicate": 1, "needs_review": 0 },
"result": "partially_completed"
}
}
```
| Field | Type | Description |
| - | - | - |
| `data.id` | string | Batch id. Read the details with `GET /invoices/income/batch/{id}` |
| `data.livemode` | boolean | Mode of the credential that created the batch |
| `data.total` | integer | Invoices in the request |
| `data.accepted` | integer | Invoices that passed validation and were processed |
| `data.rejected` | integer | **Number** of invoices refused by validation. On the batch object, `rejected` is the list |
| `data.counts` | object | Accepted invoices by final status: `stamped`, `failed`, `duplicate`, `needs_review` (`queued` is `0`) |
| `data.result` | string | `completed`, `partially_completed` or `failed`. See [Batch result](/guides/invoice-batches#batch-result) |
## Delivery Behavior
Deliveries go to every **active** webhook subscribed to the event. Order across events is not guaranteed, and the same event can arrive more than once.
**Resource events**
* Each attempt times out after **10 seconds**.
* A `2xx` response counts as delivered.
* `408`, `429`, `5xx`, timeouts and network errors are retried with exponential backoff, up to 16 attempts in total.
* Any other `4xx` is final and is not retried, so don't return a `4xx` for a failure you want redelivered.
* Webhooks are never disabled automatically because of failed deliveries.
**`sat.invoice.synced` and `invoice_batch.completed`**
* One attempt with a 10-second timeout. It is **never retried**, and your response is ignored.
A delivery can still be missed, so treat webhooks as notifications and reconcile periodically against the API, for example with `GET /payments`, `GET /invoices/sat` for SAT invoices, or `GET /invoices/income/batch/{id}` for a batch.
## Verifying Signatures
Only `sat.invoice.synced` and `invoice_batch.completed` deliveries are signed. To authenticate resource events, see [Headers](#headers). Compute the HMAC over the **raw request bytes**, before any JSON parsing, and compare it in constant time. The key is the `secret` string exactly as returned when you created the webhook.
### Node.js / Express
```javascript theme={null}
const crypto = require('crypto')
const express = require('express')
const app = express()
const WEBHOOK_SECRET = process.env.GIGSTACK_WEBHOOK_SECRET // the `secret` returned by POST /webhooks
// express.raw keeps the body as a Buffer so the signature is computed over the exact bytes sent
app.post('/webhooks/gigstack', express.raw({ type: 'application/json' }), (req, res) => {
const received = req.get('X-Gigstack-Signature') || ''
const expected = 'sha256=' + crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex')
const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
if (!valid) return res.status(401).end()
const event = JSON.parse(req.body.toString('utf8'))
// Acknowledge quickly (gigstack waits at most 10 seconds), then process
res.status(200).end()
handleGigstackEvent(event).catch(console.error)
})
const processed = new Set() // use a persistent store in production
async function handleGigstackEvent(event) {
if (processed.has(event.id)) return // de-duplicate on the event id
processed.add(event.id)
switch (event.event) {
case 'sat.invoice.synced':
await importSatInvoice(event.data.uuid, event.data.direction)
break
default:
console.log(`Unhandled event type: ${event.event}`)
}
}
app.listen(3000)
```
### Python / Flask
```python theme={null}
import hashlib, hmac, json, os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['GIGSTACK_WEBHOOK_SECRET'].encode()
@app.route('/webhooks/gigstack', methods=['POST'])
def gigstack_webhook():
raw = request.get_data() # raw bytes, before JSON parsing
expected = 'sha256=' + hmac.new(WEBHOOK_SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(request.headers.get('X-Gigstack-Signature', ''), expected):
abort(401)
event = json.loads(raw)
if event['event'] == 'sat.invoice.synced':
queue_import(event['data']['uuid']) # hand off; respond within 10 seconds
return '', 200
```
### PHP
```php theme={null}
The API keys in the response are shown **once**. Store them at that moment.
If you are creating a team that will later connect to the SAT — especially a connected team under gigstack Connect — read [the RFC guidance](/guides/gigstack-connect#choose-the-right-rfc-before-you-create-the-team) before you pick the `rfc`. Getting it wrong is only discovered much later, at FIEL upload.
## Key Features
* **Mexican Tax Compliance** - Full SAT, RFC, and CFDI 4.0 support
* **Invoice Lifecycle** - Create, stamp, and cancel invoices
* **Payment Processing** - Multiple processors with refund support
* **Client Management** - Fiscal validation and EFOS checking
* **Service Catalog** - Products with tax configurations
* **gigstack Connect** - Multi-team resource management
## gigstack Connect
A **master team** can act on other teams that share its billing account by adding the `team` query parameter to a request:
```bash theme={null}
GET /clients?team=team_xyz789
# Create invoice for team_abc123
POST /invoices/income?team=team_abc123
```
**Requirements:**
* Your API key's team must be a master team (gigstack Connect enabled)
* The target team must exist and share the same billing account
* Your plan must include the `multipleIssuerAccounts` feature
* Use an **API key**: OAuth access tokens are bound to one team, and any other `team` value is rejected with `403 Team mismatch with OAuth token`
**Errors** (raw `{ "message": … }` bodies from the authentication layer):
| Status | `message` | Cause |
| - | - | - |
| `401` | `Unauthorized, not a master team` | Your key's team does not have gigstack Connect |
| `404` | `Team not found` | The target team does not exist |
| `401` | `Unauthorized, no matched teams` | The target team could not be resolved within your billing account |
| `403` | `Tu plan no incluye múltiples cuentas emisoras. …` | Your plan lacks `multipleIssuerAccounts` |
| `403` | `Team mismatch with OAuth token` | An OAuth access token was sent with another team's id |
See the [gigstack Connect guide](/guides/gigstack-connect) for details.
## Response Format
Most endpoints return the **standardized envelope**. `timestamp` is epoch milliseconds; `message` is present only when the endpoint sets one.
### Success Response
```json theme={null}
{
"success": true,
"data": {},
"message": "Description of action",
"timestamp": 1767225600000
}
```
### Error Response
```json theme={null}
{
"success": false,
"error": {
"code": "validation_failed",
"message": "Request validation failed",
"details": ["currency: Field is required"]
},
"timestamp": 1767225600000
}
```
`error.code` is a stable, machine-readable value (`validation_failed`, `invalid_request_body`, `unauthorized`, `forbidden`, `resource_not_found`, `resource_conflict`, `internal_server_error`, …). Branch on it and on the HTTP status, not on `message`.
### List Response
List endpoints put `data` and the pagination keys at the **top level**:
```json theme={null}
{
"success": true,
"message": "Services retrieved successfully",
"data": [],
"next": "cursor_for_next_page",
"has_more": true,
"total_results": 150,
"timestamp": 1767225600000
}
```
Pass `next` back as the `next` query parameter to get the following page. `limit` defaults to **10** (max 100) unless an endpoint says otherwise. Search endpoints (`/search`) report `found`, `page` and `per_page` instead of a cursor.
### Exceptions
Not every endpoint uses the envelope: authentication failures (above), invoice creation and stamping (`{ "message", "error" }`), the SAT and bulk-download endpoints (`{ "success", "message", "data" }` with no `timestamp`) and the health probes return their own shapes. Each operation in the [API reference](https://docs.gigstack.io) documents the exact body it returns.
## Test Mode
Every API key is either **live** or **test**; the mode is fixed and both use the same host (`https://api.gigstack.io/v2`). Create your test key at [app.gigstack.pro/settings?tab=api](https://app.gigstack.pro/settings?tab=api).
* **Separate data.** Resources are stored with the mode of the key that created them, and list and search endpoints only return resources of your key's mode.
* **Crossing modes is refused.** For example, cancelling a live invoice with a test key answers `403` (`Livemode mismatch`).
* **Separate folios.** Live and test keep independent folio counters for each series.
* **Live-only operations.** `POST /invoices/eom/run` and `POST /teams` reject test keys.
* **CFDI stamping in test mode is not confirmed.** For teams that stamp through Facturapi, test-mode invoices are sent with the team's Facturapi *test* key. Whether a test-mode CFDI ever reaches the SAT, whether it is a valid fiscal document, and how teams on other providers behave is not confirmed yet. Do not hand test-mode invoices to customers as fiscal documents.
## Rate Limits
These limits answer `429`. No `Retry-After` header is sent.
| Limit | Endpoints |
| - | - |
| Team credit limit (`credit_limit` on the team) | `POST /invoices/income`, `POST /invoices/draft/{id}/stamp`, `POST /receipts` |
| 10 manual SAT download requests per team per day (resets at midnight, Mexico City) | `POST /invoices/download/request` |
The credit limit counts issued documents, not requests, and is checked before stamping, so a rejected call consumes no folio. Retry `429` and `503` with exponential backoff. Other limits may apply at the infrastructure level.
## API Resources
| Resource | Description | Guide |
| - | - | - |
| **Clients** | Manage clients with fiscal information | [clients.md](/guides/clients) |
| Services | Product/service catalog with SAT codes | [services.md](/guides/services) |
| Invoices | CFDI 4.0 compliant invoicing | [invoices.md](/guides/invoices) |
| Retentions | Withholding tax certificates (constancias de retenciones) | [retentions.md](/guides/retentions) |
| Platform Payouts | Marketplace provider payouts invoiced from two files | [platform-payouts.md](/guides/platform-payouts) |
| Descarga Masiva | Bulk-download your full SAT invoice history via FIEL | [descarga-masiva.md](/guides/descarga-masiva) |
| Payments | Payment processing and tracking | [payments.md](/guides/payments) |
| Receipts | Self-service invoice generation | [receipts.md](/guides/receipts) |
| Documents | Supporting documents for SAT compliance | [documents.md](/guides/documents) |
| SAT Lists | Screen RFCs against the SAT's 69 / 69-B / 69-B Bis lists | [sat-lists.md](/guides/sat-lists) |
| Teams | Team management and settings | [teams.md](/guides/teams) |
| Users | User account management | [users.md](/guides/users) |
| Webhooks | Real-time event notifications | [webhooks.md](/guides/webhooks) |
| gigstack Connect | Multi-team resource access via `?team=` | [gigstack-connect.md](/guides/gigstack-connect) |
| SAT Catalogs | Coded values CFDI requires | [catalogs/](/guides/catalogs/payment_forms) |
## Quick Examples
### Create a Client
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/clients \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Juan Pérez García",
"email": "juan.perez@ejemplo.com",
"tax_id": "PEGJ800101ABC",
"tax_system": "601"
}'
```
### Create an Invoice
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/invoices/income \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"automation_type": "payment",
"client": {"id": "client_1234567890"},
"currency": "MXN",
"items": [{
"description": "Consulting services",
"quantity": 1,
"unit_price": 1000.00,
"product_key": "80141503",
"unit_key": "E48",
"taxes": [{
"type": "IVA",
"rate": 0.16,
"withholding": false
}]
}],
"use": "P01",
"payment_form": "03",
"payment_method": "PUE"
}'
```
> **Note:** `exchange_rate` is optional. If not provided, the latest rate from our rates collection will be used automatically.
### Register a Payment
```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client": {"id": "client_1234567890"},
"automation_type": "pue_invoice",
"currency": "MXN",
"payment_form": "03",
"items": [{
"id": "service_1234567890",
"quantity": 1
}],
"idempotency_key": "payment-12345"
}'
```
> **Note:**
>
> * `exchange_rate` is optional. If not provided, the rate from the payment date will be fetched automatically from our rates collection.
> * `date` is optional. Use it to backdate a payment (must be in the past). Defaults to current time.
## Development Tools
* **Swagger UI**: Interactive API documentation
* **Postman Collection**: Pre-configured requests
* **Code Examples**: Available in multiple languages
* **Webhooks**: Real-time event notifications
## Support
* **Documentation**: [docs.gigstack.io](https://docs.gigstack.io)
* **Support Email**: [support@gigstack.io](mailto:support@gigstack.io)
* **API Status**: [status.gigstack.io](https://status.gigstack.io)
***
Ready to start building? Choose a resource from the guides above to begin your integration.
# Build with gigstack
Source: https://docs.gigstack.io/index
Connect your customers, payments, and CFDI invoices.
Start with one API request. Then follow the guide for what you want to build.
Get a test key and read your customers in a few minutes.
Give your coding agent the API reference, Markdown guides, and task boundaries.
Create a draft, check the fiscal data, and issue a CFDI.
Register a payment or create a payment request.
## How the resources fit together
A **client** holds customer and fiscal details. A **service** describes what you sell.
An **invoice** records the sale as a CFDI. A **payment** tracks money received or requested.
A **receipt** lets a customer request an invoice later.
For multiple issuing businesses, use [gigstack Connect](/guides/gigstack-connect).
For status changes, start with [webhooks](/guides/webhooks).
## One API base URL
```text theme={null}
https://api.gigstack.io/v2
```
Send your key in `Authorization: Bearer YOUR_API_KEY`. Live and test keys use the same URL;
the key determines the data mode. See [authentication](/authentication) before making writes.
Use the **API reference** tab for request fields, response schemas, and examples for each endpoint.
# Your first API request
Source: https://docs.gigstack.io/quickstart
Read customers with a test key, then create a test customer.
Open [API settings](https://app.gigstack.pro/settings?tab=api) and create a **test** key.
Keep it in your local environment or secret manager.
Set `GIGSTACK_API_KEY` in your shell, then run:
```bash theme={null}
curl --fail-with-body --get 'https://api.gigstack.io/v2/clients' \
-H "Authorization: Bearer $GIGSTACK_API_KEY" \
--data-urlencode 'limit=10'
```
A successful request returns HTTP `200` with a `data` array. An empty array is expected
if this key's team has no test customers. A `401` means authentication failed; a `403`
can mean your account or plan does not permit the request.
This request creates a customer record. It does not issue an invoice.
```bash theme={null}
curl --fail-with-body 'https://api.gigstack.io/v2/clients' \
-H "Authorization: Bearer $GIGSTACK_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"name":"Example customer","email":"customer@example.com","metadata":{"external_id":"quickstart-customer-1"}}'
```
Save the returned customer's `id` for later requests. Repeating this request can create
another record; see the [clients guide](/guides/clients) for finding existing customers.
## Next: an invoice draft
Before issuing a CFDI, complete the customer's fiscal information and your team's SAT setup.
Follow the [invoice guide](/guides/invoices) to prepare a draft and inspect it before stamping.
A test key selects test data, but provider-specific stamping behavior still needs verification.
Do not treat a test key as a guarantee that a fiscal operation has no external effects.
# Analyze document with AI
Source: https://docs.gigstack.io/reference/analyzeDocument
POST /documents/{id}/analyze
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Run AI extraction over the document and store the result on it under `ai_extraction`.
Only PDFs and PNG/JPEG/WEBP images can be analyzed. For PDFs the text layer is
extracted first — a scanned PDF with no selectable text is rejected with `400`.
The request body is ignored; the prompt is derived from the document's `documentType`.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Health check
Source: https://docs.gigstack.io/reference/authHealth
GET /auth/health
Liveness probe for the auth module. Unauthenticated — the whole auth router is mounted
as a public endpoint, so no credential is required or inspected.
# Cancel invoice
Source: https://docs.gigstack.io/reference/cancelInvoice
DELETE /invoices/{id}
Cancel a specific invoice with SAT.
**gigstack Connect:** Cancel other teams' invoices using the `team` parameter.
# Stop a running job
Source: https://docs.gigstack.io/reference/cancelInvoicesDownloadJob
POST /invoices/download/jobs/{id}/cancel
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Marks the job cancelled. The worker checks between windows, so it stops after finishing the one in flight rather than immediately. Already-completed jobs are returned unchanged.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Health check
Source: https://docs.gigstack.io/reference/catalogsHealth
GET /catalogs/health
Liveness probe for the catalogs module. Unauthenticated.
# Health check
Source: https://docs.gigstack.io/reference/clientsHealth
GET /clients/health
Liveness probe for the clients module. Unauthenticated.
# Confirm a platform payouts run for stamping
Source: https://docs.gigstack.io/reference/confirmPlatformPayoutRun
POST /platform-payouts/{id}/confirm
**Irreversible.** Hands a `plan_ready` run to the background worker, which stamps its CFDIs (it runs
every minute). A stamped CFDI can only be cancelled, not undone. The response only acknowledges the
hand-off; follow progress with `GET /platform-payouts/{id}` until `status` is `completed` or
`failed`, then read its `result`.
**Safe to retry.** Confirming a run that is already `stamping` or `completed` returns its current
status with `200` and does nothing else.
**Provider-months already certified.** Before the run flips to `stamping`, each provider-month it
certifies is reserved. If an earlier run of your team (same mode) already reserved a provider-month,
this run's retention certificates for it are skipped (their `reason` names the earlier run,
`reason_code` is `certificate_month_reserved`) and the counts in `included_count`,
`excluded_count`, `exclusion_summary`, `exclusion_code_summary` and
`planned_documents.certificate` are recomputed. Income and commission invoices are not affected.
No request body. Requires `editor` permission on invoices for user-scoped tokens.
# Connect FIEL credentials from a PFX file
Source: https://docs.gigstack.io/reference/connectFielPfx
POST /invoices/download/pfx
Register the team's FIEL (e.firma) with the bulk-download service by uploading a
PKCS#12 / PFX bundle and its password.
> ### ⚠️ Sensitive credentials
> `pfx` and `pfx_password` are **live security credentials** — the PFX embeds the FIEL
> private key, and the password unlocks it. Together they can impersonate the taxpayer
> before the SAT.
>
> - Send them only over TLS, only to this endpoint.
> - Never log them, never put them in a URL, never commit them, never paste them into a
> shared document or ticket.
> - The example values below are **placeholders**, not usable credentials. Do not treat
> any example in this document as a real secret to copy.
>
> The server encrypts both values at rest and never returns them in any response.
The certificate is validated before anything is stored: it must parse with the supplied
password, must not be expired, and its RFC must match the team's configured RFC. Each
RFC needs its own team.
# Create client
Source: https://docs.gigstack.io/reference/createClients
POST /clients
Create a new client with fiscal information for Mexican tax compliance.
**Duplicate Prevention (Upsert):** Use the `search` parameter to find existing clients before creating:
- If a match is found and `search.update` is `false` (default): Returns the existing client without modifications.
- If a match is found and `search.update` is `true`: Updates the existing client with the provided data and returns it.
- If no match is found: Creates a new client.
This is useful for integrations that may send the same client multiple times.
**gigstack Connect:** Create clients for other teams using the `team` parameter.
# Upload support document
Source: https://docs.gigstack.io/reference/createClientsByIdSupportDocuments
POST /clients/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Upload a supporting document (contract, proof of delivery, etc.) for a client.
**SAT 2026 Compliance:** Documents uploaded to a client are automatically inherited by all
of the client's invoices and payments, simplifying compliance management.
**gigstack Connect:** Upload documents for other teams' clients using the `team` parameter.
**Supported File Types:**
- PDF files (.pdf)
- Images (.png, .jpg, .jpeg, .webp)
**File Size Limit:** 10MB
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Upload CSF PDF to create or update client
Source: https://docs.gigstack.io/reference/createClientsCsf
POST /clients/csf
Upload a CSF (Constancia de Situación Fiscal) PDF file from SAT to automatically extract fiscal information and create a new client or update an existing one.
**How it works:**
1. Upload the CSF PDF file as `multipart/form-data`
2. The system extracts RFC and CIF from the PDF
3. Validates the fiscal information against SAT
4. Creates a new client or updates an existing one with the fiscal data
**Query Parameters:**
- `client_id` (optional): If provided, updates the existing client. If omitted, creates a new client.
**Extracted Information:**
- Legal name (Razón Social)
- RFC (Tax ID)
- Fiscal regime (Régimen Fiscal)
- Fiscal type (company/individual)
- Fiscal status
- Complete address (street, exterior/interior number, neighborhood, city, state, zip code)
**gigstack Connect:** Create or update clients for other teams using the `team` parameter.
# Get client customer portal access token
Source: https://docs.gigstack.io/reference/createClientsCustomerportal
POST /clients/customerportal
Generate a secure access token for client customer portal.
**gigstack Connect:** Access other teams' customer portal using the `team` parameter.
# Validate client fiscal information
Source: https://docs.gigstack.io/reference/createClientsValidateById
POST /clients/validate/{id}
Re-runs the full SAT validation for a client on demand. Performs three independent checks in parallel
and persists the results on the client doc (`is_valid`, `efos`, `sat_status`):
1. **Fiscal validation** — attempts to stamp a test CFDI against the PAC. Detects RFC/legal_name/CP mismatches with SAT registry.
2. **EFOS check** — Art. 69-B blacklist lookup.
3. **SAT lists** — fan-out lookup across 20 Datos Abiertos lists (Art. 69 Cancelados/Firmes/No localizados/CSD sin efectos/..., Art. 69-B Definitivos/Presuntos/..., Art. 69-B Bis). Lists are refreshed weekly from `sat.gob.mx`.
**gigstack Connect:** Validate other teams' clients using the `team` parameter.
# Create document
Source: https://docs.gigstack.io/reference/createDocument
POST /documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Register a document that has already been uploaded to storage. This endpoint records
metadata — it does not accept the file itself; upload first and pass `fileUrl` and
`storagePath`.
`complianceStatus` is always set server-side to `pending_review` on create and cannot
be supplied here; change it later with `PATCH /v2/documents/{id}`.
Unknown top-level keys are rejected (`400 validation_failed`).
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Create a batch of income invoices
Source: https://docs.gigstack.io/reference/createIncomeInvoiceBatch
POST /invoices/income/batch
Accepts up to **1,000** income invoices in one request and stamps them in the background. Each item is
exactly the body of `POST /invoices/income`, and must carry its own `idempotency_key`, unique within the
batch. To send more than 1,000 invoices, send more batches, each with its own `Idempotency-Key`.
**What happens in the request.** Every item is validated against the `POST /invoices/income` body schema,
with no I/O. An invalid item is listed in `rejected` with its reason and the rest go ahead; only an empty
or missing `invoices` array, or more than 1,000 items, refuses the whole request (`400`). The answer is
`202` with the batch in `processing`; the first items may already have started.
**What happens after.** Each accepted item is stamped by the same code as `POST /invoices/income`, with
the credential that created the batch, so it fails for the same reasons (a client that doesn't exist, a
SAT rejection, the credit limit) and consumes one credit when stamped. A temporary failure (PAC
unavailable, its answer lost) is retried automatically, up to 6 attempts per item. Items run about 10 at
a time per team. Follow the batch with `GET /invoices/income/batch/{id}`, or subscribe a webhook to
`invoice_batch.completed` (body `InvoiceBatchCompletedWebhookEvent`, signed, sent once and never
retried; see the `webhookEvent` callback of `POST /webhooks`), then read the per-item results with
`GET /invoices/income/batch/{id}/items`.
**Two levels of idempotency.**
- The `Idempotency-Key` header names the **batch**. The batch id is derived from your team, the
credential's mode and the header, so the same key with the same body returns the same batch (`200`),
and nothing is created again. The same key with a different body is `409` `idempotency_key_reused`.
Bodies are compared as sent, including key order, so resend the exact same JSON.
- Each item's `idempotency_key` names the **invoice**. It is the same key `POST /invoices/income` uses:
an invoice already issued under it, by an earlier batch or a single call, is not issued again, and the
item ends `duplicate`. So a new batch that repeats items of a previous one is safe.
**gigstack Connect:** create the batch for a connected team with the `team` parameter, and read it with the
same `team`.
**Mode.** `livemode` comes only from the credential: a test key creates a test batch.
Not supported here: file uploads (CSV/XLSX), egress invoices and payment complements. If most of your
sales are to the general public, the monthly global invoice (*factura global*) that gigstack already
produces may be all you need.
# Resend invoice email
Source: https://docs.gigstack.io/reference/createInvoicesByIdSend
POST /invoices/{id}/send
Resend an invoice email (PDF + XML attachments) to the client and/or additional recipients.
The client's email on the invoice is always included. Extra recipients can be added via the `emails` field.
**gigstack Connect:** Send other teams' invoice emails using the `team` parameter.
# Upload support document
Source: https://docs.gigstack.io/reference/createInvoicesByIdSupportDocuments
POST /invoices/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Upload a supporting document (contract, proof of delivery, etc.) for an invoice.
**SAT 2026 Compliance:** The Mexican tax authority (SAT) can request supporting documentation
to validate invoices. This endpoint helps maintain compliance.
**gigstack Connect:** Upload documents for other teams' invoices using the `team` parameter.
**Supported File Types:**
- PDF files (.pdf)
- Images (.png, .jpg, .jpeg, .webp)
**File Size Limit:** 10MB
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Activate Descarga Masiva SAT
Source: https://docs.gigstack.io/reference/createInvoicesDownloadActivate
POST /invoices/download/activate
Activate Descarga Masiva SAT for your team. Billing is adjusted automatically based on your plan:
- **Plan includes feature** (`needs_activation` status): Activation is free — you only pay $0.20 MXN per XML downloaded.
- **Plan without feature** (`needs_addon` status): Activation adds only the $0.20 MXN per XML download meter to your subscription. There is no monthly base fee.
Once activated, upload your FIEL via `POST /invoices/download/fiel` to complete setup.
# Deactivate Descarga Masiva SAT
Source: https://docs.gigstack.io/reference/createInvoicesDownloadDeactivate
POST /invoices/download/deactivate
Deactivate Descarga Masiva SAT for your team. Scheduled downloads stop immediately. If the feature was billed as an add-on, the charge is removed from your subscription (prorated). Any remaining XML downloads in the current period are still billed at period end.
# Enable SAT sync
Source: https://docs.gigstack.io/reference/createInvoicesDownloadEnableSync
POST /invoices/download/enable-sync
Enables automatic SAT synchronization for your team. This is a lower-level toggle — in most flows the schedule configuration (`PUT /invoices/download/schedule`) is the right endpoint to use.
# Upload FIEL credentials
Source: https://docs.gigstack.io/reference/createInvoicesDownloadFiel
POST /invoices/download/fiel
Upload your FIEL (Firma Electrónica Avanzada) credentials to enable SAT bulk downloads.
**This is the main setup endpoint.** It accepts your `.cer` and `.key` files, validates them, and — if `sync_start_date` and `phone` are provided — automatically registers your business with the SAT in the same request. No separate `/register` call needed.
**What it does:**
1. Validates the certificate format, extracts your RFC, and checks it matches your gigstack team RFC
2. Verifies the certificate is not expired
3. Securely encrypts and stores your credentials
4. If `sync_start_date` + `phone` are provided → registers your business with the SAT immediately and enables sync (`registered: true` in the response)
**Request format:** `multipart/form-data`
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cert` | file | Yes | `.cer` file (DER-encoded certificate from SAT) |
| `key` | file | Yes | `.key` file (DER-encoded encrypted private key from SAT) |
| `password` | string | Yes | Password for the `.key` file |
| `sync_start_date` | string | Recommended | Start date for SAT sync (`YYYY-MM-DD`, up to 71 months back) |
| `phone` | string | Recommended | Contact phone in international format (e.g. `+5215512345678`) |
> **Note:** The FIEL is different from the CSD (Certificado de Sello Digital). The CSD is used to stamp CFDI invoices. The FIEL is used to authenticate with the SAT for bulk downloads.
# Preview a range of SAT history before paying for it
Source: https://docs.gigstack.io/reference/createInvoicesDownloadPreview
POST /invoices/download/preview
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Queues a metadata pull for a date range and reports how many CFDIs exist and what downloading them would cost.
Reading header data from the SAT costs nothing; only fetching an XML is billed. This endpoint uses that gap: it stores what it finds as browsable rows in the `metadata` stage, which you can list with `GET /invoices/sat?sync_state=metadata` and then selectively import.
Answers **202**, not 200. One month is roughly a twelve second round trip to the SAT and a full history is dozens of them, well past any HTTP timeout. Poll `GET /invoices/download/jobs/{id}` for progress.
Only one job may run per team at a time.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Register business with SAT (manual)
Source: https://docs.gigstack.io/reference/createInvoicesDownloadRegister
POST /invoices/download/register
Manually register your business with the SAT. Only needed if the automatic registration during `POST /invoices/download/fiel` failed.
**In most cases you don't need to call this directly** — `POST /invoices/download/fiel` handles registration automatically when `sync_start_date` and `phone` are provided.
Requires that FIEL credentials were already uploaded via `POST /invoices/download/fiel`.
# Create bulk download request
Source: https://docs.gigstack.io/reference/createInvoicesDownloadRequest
POST /invoices/download/request
Submit a bulk download request to the SAT. The SAT processes these asynchronously — use `GET /invoices/download/schedule/history` to poll for status updates.
**Prerequisites:** FIEL uploaded (`fiel_uploaded: true`) + business registered (`registered: true`) + Descarga Masiva activated.
**Date range:** SAT limits each request to a maximum of 1 month. For longer periods, submit one request per month.
Once the request reaches `completed` status, the invoice metadata is available in the history response (`invoiceCount`, `processedCount`, `latestIssueDate`, `earliestIssueDate`).
# Create draft invoice (pre-factura)
Source: https://docs.gigstack.io/reference/createInvoicesDraft
POST /invoices/draft
Create a new draft invoice (pre-factura) that can be edited and stamped later.
Only the `invoice_type` field is required. All other fields are optional, allowing you to build the invoice incrementally:
1. **Create** a draft with minimal data (`invoice_type`)
2. **Update** the draft as data becomes available (client, items, payment details)
3. **Preview** the draft to generate a PDF with "Sin Validez Fiscal" watermark
4. **Stamp** the draft when ready to finalize it into a valid CFDI
**gigstack Connect:** Create drafts for other teams using the `team` parameter.
# Generate draft preview PDF (pre-factura)
Source: https://docs.gigstack.io/reference/createInvoicesDraftByIdPreview
POST /invoices/draft/{id}/preview
Generate a preview PDF for a draft invoice with a **"Sin Validez Fiscal"** watermark.
This is the **pre-factura** feature: it runs the full CFDI pipeline (normalization, XML mapping, PDF generation) without actually stamping with SAT. The generated PDF is saved and can be retrieved later via `GET /invoices/draft/{id}`.
**Requirements:**
- Draft must have at least a `client` and one `item`
**What you get:**
- A PDF that looks like a real CFDI invoice
- UUID shows `PREFACTURA-0000-0000-0000-SINVALIDEZ`
- "Sin Validez Fiscal" watermark overlay
- Useful for client approval before stamping
# Stamp draft invoice (finalize)
Source: https://docs.gigstack.io/reference/createInvoicesDraftByIdStamp
POST /invoices/draft/{id}/stamp
Stamp (finalize) a draft invoice into a valid CFDI with SAT.
The draft **must** have all required fields before stamping:
- `client` — a valid client with fiscal data
- `items` — at least one item
- `use` — CFDI use code (e.g., `G03` gastos en general, `S01` sin efectos fiscales)
- `payment_form` — payment form code (e.g., 03, 99)
- `payment_method` — PUE or PPD
- `currency` — currency code (e.g., MXN, USD)
After stamping:
- The draft document is deleted
- A new stamped invoice document is created with a UUID from SAT
- The response follows the same format as `POST /invoices/income`
**gigstack Connect:** Stamp other teams' drafts using the `team` parameter.
# Create egress invoice
Source: https://docs.gigstack.io/reference/createInvoicesEgress
POST /invoices/egress
Create a new egress invoice (expense/credit note) with CFDI 4.0 compliance.
**gigstack Connect:** Create invoices for other teams using the `team` parameter.
# Create income invoice
Source: https://docs.gigstack.io/reference/createInvoicesIncome
POST /invoices/income
Create a new income invoice with CFDI 4.0 compliance.
**Safe retries with `idempotency_key`.** Send your own identifier for the invoice (for example your
order id) in `idempotency_key`. gigstack claims the key before charging a credit or stamping, so
repeating the request cannot issue a second CFDI or charge twice:
- The invoice already exists: `400` with `message.duplicate: true` and `message.uuid`.
- Another request with the key is still being processed: `409` `idempotency_in_progress`. Retry later.
- The PAC's answer was lost: `503` `PAC_OUTCOME_UNKNOWN` with `retryable: true`. Retry with the same
key: the same XML and folio are sent again, so the PAC stamps it once or returns the stamp it already made.
- The PAC can't confirm an earlier attempt: `409` `STAMP_NEEDS_REVIEW`. Don't retry; contact support.
- The SAT or PAC rejected the data: `400`, and the key is free again for a corrected request.
Without an `idempotency_key` none of this applies, and after a `503` `PAC_OUTCOME_UNKNOWN`
(`retryable: false`) you can't tell whether the invoice exists: look it up before sending it again.
To issue many invoices at once, use `POST /invoices/income/batch`.
**gigstack Connect:** Create invoices for other teams using the `team` parameter.
# Create payment complement (complemento de pago)
Source: https://docs.gigstack.io/reference/createInvoicesPayment
POST /invoices/payment
Stamp a CFDI type "P" payment complement (complemento de pago, Pagos 2.0) that
registers one or more payments against PPD (Pago en Parcialidades o Diferido) invoices.
Each entry in `complements[].data` is a payment. Each payment links one or more PPD
invoices through `related_documents`, supplying the amount paid, the installment number
and the previous balance so the SAT can compute the remaining balance.
**Fixed by the SAT** and therefore not required in the body: the comprobante currency
(`XXX`), the receptor `UsoCFDI` (`CP01`), and the line concept. The per-payment currency
lives in each payment's `currency` field. The series defaults to the team's payments
series (`invoice_serie_payments`).
**gigstack Connect:** Create for other teams using the `team` parameter.
# Mark payment as paid
Source: https://docs.gigstack.io/reference/createPaymentsByIdPaid
POST /payments/{id}/paid
Mark a payment as paid with the specified payment form.
**gigstack Connect:** Mark other teams' payments as paid using the `team` parameter.
## Required Information
- **payment_form** (required): SAT-compliant payment form code (`01`–`31`, `99`)
- **date** (optional): when the payment was received, in **Unix epoch milliseconds**
(13 digits). The handler passes the value straight to `Luxon.fromMillis()` and
compares it against `Luxon.now().toMillis()`; a seconds-based timestamp resolves to
1970 and is silently accepted. Defaults to now. A future date returns `400`.
- **send_email** / **ignore_emails** (optional): `ignore_emails` takes precedence —
the handler resolves `ignore_emails ?? (send_email === false)`, so `ignore_emails: true`
suppresses notifications even when `send_email: true`.
- **amount_received** (optional): cumulative amount received so far, in the payment's
currency. Omit it, or send the full payment amount, to mark the payment `succeeded`
(unchanged default behavior). Send less than the full amount to record a partial
top-up: the payment is set to `partially_paid` instead, triggering a partial payment
complement on the related PPD invoice. Requires the team's
`automatePartialPaymentComplements` default to be on and a `payment_complement`
automation already present on the payment. Sending more than the payment amount
returns `400`.
# Refund payment
Source: https://docs.gigstack.io/reference/createPaymentsByIdRefund
POST /payments/{id}/refund
Refund a payment with a specified reason and amount.
**gigstack Connect:** Refund other teams' payments using the `team` parameter.
## Key Features
- Partial or full refunds supported
- Optional external processor refund handling
- Automatic refund tracking and reporting
- Supports Stripe integration for automatic processor refunds
`reason` and `amount` are both required. `amount` must be at least **0.01** — the
validator rejects anything below it.
# Upload support document
Source: https://docs.gigstack.io/reference/createPaymentsByIdSupportDocuments
POST /payments/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Upload a supporting document (contract, proof of delivery, etc.) for a payment.
**SAT 2026 Compliance:** The Mexican tax authority (SAT) can request supporting documentation
to validate invoices and payments. This endpoint helps maintain compliance.
**gigstack Connect:** Upload documents for other teams' payments using the `team` parameter.
**Supported File Types:**
- PDF files (.pdf)
- Images (.png, .jpg, .jpeg, .webp)
**File Size Limit:** 10MB
**Document Types:**
- `contract`: Service or product contracts
- `delivery_proof`: Proof of delivery or service completion
- `payment_proof`: Payment receipts or confirmations
- `communication`: Emails, messages, or agreements
- `payment_confirmation`: Payment processor confirmations
- `subscription_info`: Subscription or recurring service details
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Register payment
Source: https://docs.gigstack.io/reference/createPaymentsRegister
POST /payments/register
Register a payment with optional automation for invoice creation.
**gigstack Connect:** Register payments for other teams using the `team` parameter.
## Automation Types
Control what happens automatically when registering a payment:
- **`pue_invoice`**: Creates a PUE (Pago en Una sola Exhibición) invoice immediately
- **`none`**: No automation, registers payment only
## PPD Invoice Linking
You can link a payment to an existing PPD (Pago en Parcialidades o Diferido) invoice by providing the `ppd_invoice_id` field.
When set, a payment complement (complemento de pago) CFDI will be automatically generated and linked to the PPD invoice.
The referenced invoice must have `payment_method='PPD'` and `status='valid'`.
## Payment Form
The `payment_form` field specifies the Mexican SAT payment form code:
Common codes include: `01` (cash), `02` (check), `03` (electronic transfer), `04` (credit card), etc.
The payment will be marked as 'succeeded' immediately upon registration.
## Required fields
`client`, `currency`, `items` (at least one), `payment_form` and **`automation_type`**
are required. `automation_type` has no default — omitting it fails validation.
Allowed values: `pue_invoice`, `ppd_invoice_and_complement`, `none`.
## `date`
Optional, in **Unix epoch milliseconds** (13 digits). Compared against
`Luxon.now().toMillis()`; a future value returns `400`.
## `transfer_data` — all-or-nothing
`transfer_data` is optional, but **when it is present all four of `master`, `connect`,
`master_to` and `connect_to` are required**; omitting any one fails validation.
| field | type | constraint |
|---|---|---|
| `master` | number | required, `0 ≤ master ≤ 100` (percentage retained by the master team) |
| `connect` | string | required, non-empty — RFC of the connected team |
| `master_to` | enum | required — `client` or `connect` |
| `connect_to` | enum | required — `client` or `master` |
| `connect_custom_config` | object | optional; every field inside it is optional except `type`, `rate` and `withholding` on each `taxes[]` entry |
## `invoice_config`
All nested fields are optional:
| field | type | meaning |
|---|---|---|
| `serie` | string | invoice series |
| `folio` | number | invoice folio number |
| `date` | number | invoice issue date, Unix epoch **milliseconds** |
| `global.year` | number | fiscal year of the global (EOM) invoice, e.g. `2026` |
| `global.months` | string | SAT `c_Meses` code, e.g. `01` for January or `13` for Jan–Feb |
| `global.periodicity` | string | SAT `c_Periodicidad` code — `01` daily, `02` weekly, `03` fortnightly, `04` monthly, `05` bimonthly |
| `validUntil` | number | expiry of the self-invoicing window, Unix epoch **milliseconds** |
## Email suppression
`ignore_emails: true` suppresses notification emails. On this endpoint `send_email` is
accepted but has no effect — only `ignore_emails` is persisted onto the payment.
## Unknown fields
Body validation runs in strict allowlist mode — any undeclared key is rejected with
`400 validation_failed` / `unexpected_key`.
# Request payment
Source: https://docs.gigstack.io/reference/createPaymentsRequest
POST /payments/request
Create a payment request that creates a payment in 'requires_payment_method' status.
**gigstack Connect:** Create payment requests for other teams using the `team` parameter.
## Payment Request Flow
This endpoint creates a payment request that customers can complete using various payment methods.
The payment will be created with status 'requires_payment_method'.
## Allowed Payment Methods
`allowed_payment_methods` is required. The validator accepts exactly these five values:
- **`card`** — credit/debit card
- **`bank`** — Mexican bank transfer (SPEI)
- **`oxxo`** — OXXO convenience store
- **`stripe-spei`** — Stripe customer balance
- **`mercadopago-wallet`** — Mercado Pago wallet
The handler then narrows the list per processor and rejects anything outside the
processor's own set:
| `payment_processor` | accepted methods | currency |
|---|---|---|
| `stripe` (default) | `card`, `oxxo`, `bank`, `stripe-spei` | any |
| `mercadopago` | `card`, `oxxo`, `mercadopago-wallet` | `MXN` only |
| `openpay` | `card`, `bank_account`, `store` | `MXN` only |
| `pagoralia` | `hosted`, `card`, `oxxo` | `MXN` only |
| `conekta` | `hosted`, `card`, `oxxo`, `spei` | `MXN` only |
> The processor-specific names in the right-hand column (`bank_account`, `store`,
> `hosted`, `spei`) are **not** accepted by body validation — only the five enum values
> above pass, so those processors are effectively limited to their overlap with the enum.
## Required fields
`client`, `currency`, `allowed_payment_methods`, `items` and **`automation_type`** are
all required. `automation_type` has no default: omitting it fails validation with
`missing_required`. Allowed values: `pue_invoice`, `ppd_invoice_and_complement`, `none`.
## Email suppression
`ignore_emails` takes precedence over `send_email`: the handler stores
`ignore_emails ?? (send_email === false)`, so `ignore_emails: true` suppresses
notifications regardless of `send_email`.
## Unknown fields
Body validation runs in strict allowlist mode — any key not declared in the schema is
rejected with `400 validation_failed` / `unexpected_key`.
# Upload files and plan a platform payouts run
Source: https://docs.gigstack.io/reference/createPlatformPayoutRun
POST /platform-payouts
Uploads the **movements** and **commissions** files and computes the plan: which CFDIs will be
issued for each provider, and why any row is excluded. Nothing is stamped here; review the plan with
`GET /platform-payouts/{id}` and `GET /platform-payouts/{id}/movements`, then call
`POST /platform-payouts/{id}/confirm`.
**Who can call it.** The credential's team must be a marketplace **master team** whose billing
account has platform payouts enabled. API keys and OAuth tokens act as the team; user-scoped
tokens (MCP, dashboard) must belong to the team and hold `editor` permission on invoices.
Sending gigstack Connect's `team` parameter moves the request to a connected team, which is not a
master team, so it is refused with `not_master_team`. These checks (`403` `not_master_team`,
`no_billing_account`, `feature_disabled`, `team_not_found`, `not_a_member`) run right after the
`Idempotency-Key` check and **before the upload is read**: a refused request's files are never
processed or stored.
**Planning is synchronous.** The response carries the run with status `plan_ready`, or
`plan_failed` with a Spanish `error` (unreadable file, missing columns, empty file, too many rows).
Row-level problems do not fail the plan: the row becomes an excluded movement with a reason.
**Idempotent on `Idempotency-Key` (required).** The run id is derived from your team, the key's
mode and the header value, and the run records a fingerprint of the two files' **contents** (their
bytes; file names don't count). The first request creates the run (`201`). A later request with the
same key and the same files returns that same run (`200`), whatever its status, without planning
again. The same key with **different** files is refused with `409 idempotency_key_reused`. Runs
created before the fingerprint existed have none and are returned as before (`200`) whatever files
you send. A retry that arrives while the first request is still planning gets the run in
`planning`: poll `GET /platform-payouts/{id}`. A failed plan stays failed under its key: fix the
file and send a **new** key.
**Tax policy.** Which documents each provider gets, and their SAT keys and withholding rates,
come from your master account's policy, set at onboarding (defaults: ground passenger transport,
régimen `625`, CSD required, service type `01`, 2.1% ISR). Contact support to configure it.
**Mode.** `livemode` comes only from the credential: a test key creates a test run.
# Create receipt
Source: https://docs.gigstack.io/reference/createReceipts
POST /receipts
Create a new receipt with items and client information. Receipts are pre-invoice documents
that can be later stamped as CFDI invoices.
**Features:**
- Automatic amount calculations with taxes
- Flexible validity periods
- Client auto-creation support
- Metadata support for tracking
- Idempotency support to prevent duplicate receipts
**gigstack Connect:** Create receipts for other teams using the `team` parameter.
# Reopen receipt
Source: https://docs.gigstack.io/reference/createReceiptsByIdReopen
POST /receipts/{id}/reopen
Return a receipt whose self-invoicing failed to `status: pending`, so the client can
try again from the self-invoicing portal.
Only a receipt that is **not** `pending` and has **no** entries in `invoices[]` can be
reopened: an already-pending receipt and one that produced a CFDI are both rejected
with `409`. The response clears `automatic_invoice_error`; who reopened it and why are
recorded on the receipt but are not part of the API response.
**gigstack Connect:** reopen other teams' receipts with the `team` parameter.
# Stamp receipt
Source: https://docs.gigstack.io/reference/createReceiptsByIdStamp
POST /receipts/{id}/stamp
Convert a receipt into a CFDI invoice by stamping it with SAT.
Only receipts with `status: pending` can be stamped. A receipt that already has
entries in `invoices[]` is returned as-is with a `200` and no new CFDI; any other
non-pending receipt (a cancelled one is stored as `completed`) is rejected with a
`409`.
**Stamp Options:**
- `client`: Stamp to the associated client
- `general_public_national`: Stamp to Mexican general public
- `general_public_foreign`: Stamp to foreign general public
**gigstack Connect:** Stamp other teams' receipts using the `team` parameter.
# Create retention
Source: https://docs.gigstack.io/reference/createRetentions
POST /retentions
Create and stamp a new tax retention document (CFDI Retenciones 2.0).
The API accepts a **simplified format** — the backend handles:
- **Client lookup** by ID (fetches RFC, legal name, address automatically)
- **Nationality detection** from client's country
- **Tax code mapping** (`ISR` → 001, `IVA` → 002, `IEPS` → 003)
- **Payment type defaults** per tax (ISR → provisional, IVA/IEPS → definitivo)
- **Totals auto-calculation** (taxable = operation − exempt, retained = sum of taxes)
- **Folio auto-generation**
- SAT stamping, PDF and XML generation
**gigstack Connect:** Create retentions for other teams using the `team` parameter.
## Conditional requirements per `retention_key`
`retention_key` is validated as a free-form string — any SAT key is accepted — but three
keys carry extra requirements enforced before the document is stamped. A violation
returns `400` with `error.code: invalid_request_body` and the message quoted below.
| `retention_key` | additional requirement | error message on violation |
|---|---|---|
| `16` — Intereses | `interest` object is required | `interest object is required for retention key 16 (Intereses)` |
| `25` — Otro tipo de retenciones | `retention_description` is required and non-empty | `retention_description is required for retention key 25 (Otro tipo de retenciones)` |
| `26` — Plataformas Tecnológicas | `platform_services` object is required **and** `taxes` must contain at least one entry whose `tax` is not `IVA` (i.e. `ISR` or `IEPS`) | `platform_services object is required for retention key 26 (Plataformas Tecnológicas)` / `At least one non-IVA tax (ISR or IEPS) is required for Plataformas Tecnológicas` |
Every other key requires only the base fields (`retention_key`, `client`,
`period_start`, `period_end`, `period_year`, `total_operation`, `taxes`).
Within `interest`, `financial_system`, `nominal_interest` and `real_interest` are
required. Within `platform_services`, `periodicity` and `services` are required, and
each entry in `services` requires `payment_form`, `service_type`, `service_date` and
`price_without_tax`.
> Unlike the other modules, this endpoint **strips** unknown keys instead of rejecting
> them (`stripUnknown: true`), so an undeclared field is silently discarded rather than
> returning `400`.
# Create service
Source: https://docs.gigstack.io/reference/createServices
POST /services
Create a new service.
**gigstack Connect:** Create services for other teams using the `team` parameter.
# Create team
Source: https://docs.gigstack.io/reference/createTeams
POST /teams
Create a new team.
Requires the `multipleIssuerAccounts` feature on your plan → `403 insufficient_permissions` without it.
**gigstack Connect:** Create teams using the `team` parameter.
Requires a plan with the "Proveedores" feature: otherwise the call answers `401` with
`You should subscribe to a plan that allows "Proveedores" feature.` (standardized envelope).
# Add team member
Source: https://docs.gigstack.io/reference/createTeamsByIdAddMember
POST /teams/{id}/add-member
Add a member to a team.
**gigstack Connect:** Add members to other teams using the `team` parameter.
# Sign manifest document
Source: https://docs.gigstack.io/reference/createTeamsByIdManifestSign
POST /teams/{id}/manifest/sign
Signs a manifest document (Carta Manifiesto) using the FIEL (Firma Electrónica Avanzada) for SAT compliance.
This endpoint is used to sign the authorization manifest that authorizes the PAC (Proveedor Autorizado de Certificación)
to issue CFDI invoices on behalf of your team's RFC. The manifest must be signed to grant the PAC permission to stamp
and process invoices under your team's tax identification.
**Important Notes:**
- Your SAT configuration must be completed before signing the manifest
- The FIEL certificate must be valid and issued by SAT
- The certificate must match your team's RFC
- Once signed, the manifest is stored in your team's SAT configuration
- The manifest includes both XML and PDF files
**Supported Formats:**
1. **JSON format (application/json):**
- Send Base64 encoded certificate files
- Useful for API integrations
2. **Form Data format (multipart/form-data):**
- Upload certificate files directly
- Useful for web form submissions
**Note:** The `team` and `livemode` parameters are automatically extracted from your JWT token and applied to the request.
You do not need to include these fields in the request body.
# Create a portal access token
Source: https://docs.gigstack.io/reference/createTeamsByIdPortalAccessToken
POST /teams/{id}/portal-access-token
Mint a short-lived, read-only access token for the team's public portal, and get a ready-to-share magic link.
The link opens the gigstack public portal (embeded.gigstack.pro) where the team can list and download its issued live-mode invoices, branded with your master team's logo and colors. The token cannot create, modify or cancel anything, and it only grants the scopes you request.
**Important:** Minting a token for a team other than your own is only available for gigstack Connect accounts (master teams), and the target team must belong to your billing account.
## Use Cases
- Embed an "invoices" section for your sub-teams inside your own product
- Generate on-demand links so a sub-team can review its issued invoices without a gigstack login
## Recommendations
- Generate the link at click time and redirect the user to it; do not store it or send it by email
- Use the default 1h expiry unless you have a longer-lived embedded session
# Remove team member
Source: https://docs.gigstack.io/reference/createTeamsByIdRemoveMember
POST /teams/{id}/remove-member
Remove a member from a team.
**gigstack Connect:** Remove members from other teams using the `team` parameter.
# Upload SAT CSD certificates
Source: https://docs.gigstack.io/reference/createTeamsByIdSatConnection
POST /teams/{id}/sat-connection
Upload SAT CSD (Certificado de Sello Digital) certificates to establish SAT connection for CFDI invoicing.
This endpoint accepts multipart form data with the certificate files and password.
## Required Files
- **cert**: Certificate file (.cer) - The public certificate
- **key**: Private key file (.key) - The encrypted private key
- **keyPass**: Password for the private key
## First-Time Connection
When this is the first SAT connection for a team (no previous SAT setup),
the system will automatically initialize default invoice series (G, NC, P, T).
**gigstack Connect:** Upload SAT certificates for other teams using the `team` parameter.
# Create team series
Source: https://docs.gigstack.io/reference/createTeamsByIdSeries
POST /teams/{id}/series
Create a series for a team.
**gigstack Connect:** Create series for other teams using the `team` parameter.
# Create user
Source: https://docs.gigstack.io/reference/createUsers
POST /users
Create a new user.
**gigstack Connect:** Create users for other teams using the `team` parameter.
# Generate login link
Source: https://docs.gigstack.io/reference/createUsersLoginLink
POST /users/login-link
Generate a login link for a user. The link contains a custom Firebase token that allows the user to authenticate directly.
**Requirements:**
- User must have been created via API (`from: 'api'`)
- User must belong to the billing account making the request
If requirements are not met, returns a 404 error.
**gigstack Connect:** Generate login links for other teams' users using the `team` parameter.
# Create webhook
Source: https://docs.gigstack.io/reference/createWebhooks
POST /webhooks
Create a new webhook endpoint to receive event notifications.
The response includes the webhook's signing `secret`. **It is shown only once**, so store it
immediately. It signs only `sat.invoice.synced` and `invoice_batch.completed` deliveries, in the
`X-Gigstack-Signature` header (`sha256=` + hex HMAC-SHA256 of the raw body). Resource events are not
signed with it. There is
no endpoint to reveal or rotate the secret later; if you lose it, delete the webhook and create
a new one.
Webhooks created here send resource events in the `v1` format unless your team's default is
`v2`. Delivery formats, headers and retry behavior are described in the `webhookEvent` callback
below.
**gigstack Connect:** Create webhooks for other teams using the `team` parameter.
# Delete client
Source: https://docs.gigstack.io/reference/deleteClientsById
DELETE /clients/{id}
Delete a specific client.
**gigstack Connect:** Delete other teams' clients using the `team` parameter.
# Delete document
Source: https://docs.gigstack.io/reference/deleteDocument
DELETE /documents/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
**Soft delete.** The document is flagged `deleted` (with `deletedAt`/`deletedBy`) rather
than removed, and its id is pulled from the `satDocuments` array of every entity it was
linked to. It stops appearing in list and get responses.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Delete draft invoice
Source: https://docs.gigstack.io/reference/deleteInvoicesDraftById
DELETE /invoices/draft/{id}
Permanently delete a draft invoice. This also removes any generated preview files.
**Note:** Only drafts can be deleted. For stamped invoices, use the cancel endpoint instead.
**gigstack Connect:** Delete other teams' drafts using the `team` parameter.
# Cancel payment
Source: https://docs.gigstack.io/reference/deletePaymentsById
DELETE /payments/{id}
Cancel a specific payment.
**gigstack Connect:** Cancel other teams' payments using the `team` parameter.
# Cancel receipt
Source: https://docs.gigstack.io/reference/deleteReceiptsById
DELETE /receipts/{id}
Cancel a receipt. This action cannot be undone.
**gigstack Connect:** Cancel other teams' receipts using the `team` parameter.
# Cancel retention
Source: https://docs.gigstack.io/reference/deleteRetentionsById
DELETE /retentions/{id}
Cancel a stamped tax retention document with the SAT.
**Cancellation motives** (SAT catalog):
- `01` - Comprobante emitido con errores con relación
- `02` - Comprobante emitido con errores sin relación
- `03` - No se llevó a cabo la operación
- `04` - Operación nominativa relacionada en una factura global
**gigstack Connect:** Cancel other teams' retentions using the `team` parameter.
# Delete service
Source: https://docs.gigstack.io/reference/deleteServicesById
DELETE /services/{id}
Delete a specific service.
**gigstack Connect:** Delete other teams' services using the `team` parameter.
# Delete team
Source: https://docs.gigstack.io/reference/deleteTeamsById
DELETE /teams/{id}
Schedule a team for deletion (soft delete).
The team's `status` is set to `pending_deletion` and a hard deletion is scheduled **30 days** from now.
During this grace period the team is retained for recovery purposes. All members are removed from the team,
and each member's `teams` array is updated accordingly. A member's `billingAccount` is cleared when no
other team of theirs shares it.
Requires the `multipleIssuerAccounts` feature on your plan → `403 insufficient_permissions` without it.
## Preconditions
The team **cannot** be deleted when any of the following are true:
- The team already has `status = pending_deletion` → `409 resource_conflict`.
- The team has existing `invoices`, `payments`, or `receipts` → `400 business_rule_violation`.
- The team has any active integration (`completed = true`) among:
`stripe`, `mercadopago`, `paypal`, `openpay`, `conekta`, `clip`, `bank`, `shopify`, `whmcs`, `hilos`
→ `400 business_rule_violation`.
**gigstack Connect:** Delete other teams using the `team` parameter.
# Delete user
Source: https://docs.gigstack.io/reference/deleteUser
DELETE /users/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Remove a user from your teams, and delete their account when it is yours.
The user must be a member of the team the request is authenticated for. They are removed
from every team of your billing account you administer: your own team, and (for a
master-team API key, or a dashboard admin) its sibling teams. Memberships in teams you
do not administer are left untouched.
The login (Firebase Auth) and the `users/{id}` document are deleted only when that
leaves the user in no team at all **and** the account belongs to your billing account
(created by it, or tied to no other billing account, and not the owner of one). Otherwise
the user keeps their login, and `account_deleted` is `false`. An Auth deletion failure is
logged but does not fail the request.
**You cannot delete yourself.** If the id matches the user behind the API key the call
is rejected with `400` / `business_rule_violation`.
**gigstack Connect:** Delete other teams' users using the `team` parameter.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Delete webhook
Source: https://docs.gigstack.io/reference/deleteWebhooksById
DELETE /webhooks/{id}
Permanently delete a webhook endpoint.
**gigstack Connect:** Delete webhooks for other teams using the `team` parameter.
# Generate PDF for a received SAT invoice
Source: https://docs.gigstack.io/reference/generateSatPdf
POST /invoices/sat/{uuid}/pdf
Generates a PDF from the stored XML of a received SAT invoice.
The result is cached — subsequent calls return the cached PDF instantly.
Requires the XML to have been downloaded first (`hasXml: true`).
# Search SAT product keys
Source: https://docs.gigstack.io/reference/getCatalogsProductKeys
GET /catalogs/product-keys
Full-text search over the SAT `c_ClaveProdServ` catalog, the same catalog the dashboard
searches when you pick a default product key. Returns the codes you assign to an item's
`product_key`.
**Search Capabilities:**
- Matches on code, description and SAT taxonomy (type, division, group)
- Typo-tolerant fuzzy matching, ranked by relevance
- Paginated results
**Notes:**
- The catalog is published by the SAT and is identical for every team, so results are not
affected by `livemode` and contain no team data. Authentication is still required.
- Typesense must be configured for your team.
- The `q` (or `query`) parameter is required.
# Search SAT unit keys
Source: https://docs.gigstack.io/reference/getCatalogsUnitKeys
GET /catalogs/unit-keys
Full-text search over the SAT `c_ClaveUnidad` catalog. Returns the codes you assign to an
item's `unit_key`, together with the name commonly stored as `unit_name`.
**Search Capabilities:**
- Matches on unit key and name
- Typo-tolerant fuzzy matching, ranked by relevance
- Paginated results
**Notes:**
- The catalog is published by the SAT and is identical for every team, so results are not
affected by `livemode` and contain no team data. Authentication is still required.
- Typesense must be configured for your team.
- The `q` (or `query`) parameter is required.
# Get client
Source: https://docs.gigstack.io/reference/getClientsById
GET /clients/{id}
Retrieve a specific client by ID.
**gigstack Connect:** Access other teams' clients using the `team` parameter.
# List support documents
Source: https://docs.gigstack.io/reference/getClientsByIdSupportDocuments
GET /clients/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve all supporting documents attached to a client.
**gigstack Connect:** View documents for other teams' clients using the `team` parameter.
Documents are returned sorted by creation date (newest first).
**Note:** Client documents are automatically inherited by all the client's invoices and payments.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Search clients
Source: https://docs.gigstack.io/reference/getClientsSearch
GET /clients/search
Full-text search across clients using Typesense. Provides fast, typo-tolerant search capabilities.
**gigstack Connect:** Access other teams' clients using the `team` parameter.
**Search Capabilities:**
- Search across client name, email, tax ID, legal name, and metadata
- Typo-tolerant fuzzy matching
- Paginated results
**Requirements:**
- Typesense must be configured for your team
- The `q` (or `query`) parameter is required
# Get document
Source: https://docs.gigstack.io/reference/getDocument
GET /documents/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve one document. Documents belonging to another team, and soft-deleted
documents, are reported as `404` rather than `403`.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Get an income invoice batch
Source: https://docs.gigstack.io/reference/getIncomeInvoiceBatch
GET /invoices/income/batch/{id}
Returns the batch: the items rejected up front, and the progress of the accepted ones in `counts`. Poll it
until `status` is `completed`, then read `result`, not `status`, to know how it went: `completed`
(everything issued), `partially_completed` (some items have no invoice) or `failed` (none issued).
For each invoice's outcome, page through `GET /invoices/income/batch/{id}/items`.
A batch of another team, or of the other mode (a live batch read with a test key), answers `404`.
Under gigstack Connect, send the same `team` the batch was created with.
# Get invoice
Source: https://docs.gigstack.io/reference/getInvoiceById
GET /invoices/{id}
Retrieve any invoice by id, regardless of its CFDI type. The type-specific routes
(`/invoices/income/{id}`, `/invoices/egress/{id}`, `/invoices/payment/{id}`) share this
handler but additionally assert the document's type and return `400` on a mismatch;
this route performs no type check.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# Get invoice files
Source: https://docs.gigstack.io/reference/getInvoicesByIdFiles
GET /invoices/{id}/files
Get XML and PDF files for an invoice.
**gigstack Connect:** Access other teams' invoice files using the `team` parameter.
# List support documents
Source: https://docs.gigstack.io/reference/getInvoicesByIdSupportDocuments
GET /invoices/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve all supporting documents attached to an invoice.
**gigstack Connect:** View documents for other teams' invoices using the `team` parameter.
Documents are returned sorted by creation date (newest first).
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Check activation status
Source: https://docs.gigstack.io/reference/getInvoicesDownloadActivateStatus
GET /invoices/download/activate/status
Check whether Descarga Masiva SAT is activated for your team and what action (if any) is required.
**Possible statuses:**
- `active` — Already activated, FIEL setup and scheduling are available
- `needs_activation` — Your plan includes the feature; call `POST /invoices/download/activate` to turn it on (no extra charge)
- `needs_addon` — Your plan doesn't include the feature; activating adds the $0.20 MXN/XML download meter to your subscription (no monthly fee)
- `needs_upgrade` — Free plan; upgrade first at `/memberships`
# Debug registration status
Source: https://docs.gigstack.io/reference/getInvoicesDownloadDebug
GET /invoices/download/debug
Returns detailed debug information about your team's Descarga Masiva setup: FIEL status, registration status, schedule config, and SAT connectivity. Intended for troubleshooting.
# Get single invoice from SAT
Source: https://docs.gigstack.io/reference/getInvoicesDownloadInvoiceByUuid
GET /invoices/download/invoice/{uuid}
Fetch a single invoice's XML from the SAT by UUID. Useful for retrieving a specific CFDI without submitting a full bulk download request.
# Get one job with per-window detail
Source: https://docs.gigstack.io/reference/getInvoicesDownloadJob
GET /invoices/download/jobs/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Same shape as the list, plus the individual month windows and their state. Useful for showing which part of a long history pull is still outstanding.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Download XML package
Source: https://docs.gigstack.io/reference/getInvoicesDownloadPackageByPackageId
GET /invoices/download/package/{package_id}
Download a package of XML files for a completed download request. Package ids come from `packages[].id`
in `GET /invoices/download/status/{request_id}`.
The ZIP is returned **base64-encoded inside JSON** (`data.content`), not as a binary response. Packages
expire after a period set by the SAT — download them promptly.
# Get SAT history sync progress
Source: https://docs.gigstack.io/reference/getInvoicesDownloadProgress
GET /invoices/download/progress
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Reports how far the SAT has handed over your invoice history.
The SAT does not deliver a history all at once. It is backfilled forward from your sync start date in month-sized windows, and until that reaches the present, a query for a recent date range succeeds and returns nothing — identical to genuinely having no invoices. This endpoint is how you tell those apart.
**Fields:**
| Field | Meaning |
|-------|---------|
| `percent` | How much of the requested history has arrived |
| `covered_through` | Last date the backfill has reached |
| `months_remaining` | Roughly how much history is still pending |
| `current` | History is close enough to the present to be usable |
| `stalled` | Backfill has not advanced in over two days |
| `enabled` | Sync is active. When `false` it will not advance on its own |
| `eta_at` | Projected completion, epoch ms. Omitted while `stalled`, since a stopped sync has no meaningful estimate |
Values are refreshed periodically in the background. Pass `refresh=true` to recompute against the provider, which takes a few seconds.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Get schedule configuration
Source: https://docs.gigstack.io/reference/getInvoicesDownloadSchedule
GET /invoices/download/schedule
Returns the current scheduled download configuration plus FIEL and registration status. Use this to check setup progress before configuring a schedule or submitting download requests.
- `fiel_uploaded: true` — FIEL credentials are stored
- `registered: true` — Business is registered with the SAT, downloads are enabled
- `schedule` — Current schedule config, or `null` if not configured yet
- `fiel` — FIEL certificate metadata (RFC, expiry, serial number)
# Get download request history
Source: https://docs.gigstack.io/reference/getInvoicesDownloadScheduleHistory
GET /invoices/download/schedule/history
Returns the last 20 download requests for your team, ordered by most recent first.
**Live status check:** Any pending requests (`accepted`, `processing`, `pending`) are checked against the SAT in real time before the response is returned, so you always get up-to-date statuses in a single call.
**Statuses:**
| Status | Meaning |
|--------|---------|
| `pending` | Request queued, being sent to SAT |
| `accepted` | SAT accepted the request, processing started |
| `processing` | SAT is generating the package |
| `completed` | Done — invoice metadata is available in the response |
| `failed` | SAT rejected the request (see `statusMessage`) |
| `expired` | Package expired before it was downloaded |
# Check download request status
Source: https://docs.gigstack.io/reference/getInvoicesDownloadStatusByRequestId
GET /invoices/download/status/{request_id}
Check the current SAT processing status of a download request (an id from the `created` array of
`POST /invoices/download/request`).
When `status` is `completed`, `packages` lists the packages to download with
`GET /invoices/download/package/{package_id}`.
# List draft invoices
Source: https://docs.gigstack.io/reference/getInvoicesDraft
GET /invoices/draft
Retrieve a paginated list of draft invoices (pre-facturas).
Drafts are incomplete invoices that have not been stamped yet. Use them to prepare invoices incrementally before finalizing.
**gigstack Connect:** Access other teams' drafts using the `team` parameter.
# Get draft invoice
Source: https://docs.gigstack.io/reference/getInvoicesDraftById
GET /invoices/draft/{id}
Retrieve a specific draft invoice by ID. Includes the preview PDF (base64) if one has been generated.
**gigstack Connect:** Access other teams' drafts using the `team` parameter.
# List egress invoices
Source: https://docs.gigstack.io/reference/getInvoicesEgress
GET /invoices/egress
Retrieve a paginated list of egress invoices with powerful filtering capabilities.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
**Filtering Options:**
- Filter by creation date using comparison operators
- Filter by `status`, `series`, `folio` and `idempotency_key` (exact match only)
- Filter by metadata fields using dot or underscore notation (e.g., `metadata.order_id` or `metadata_order_id`)
# Get egress invoice
Source: https://docs.gigstack.io/reference/getInvoicesEgressById
GET /invoices/egress/{id}
Retrieve a specific egress invoice by ID.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# List CFDI errors
Source: https://docs.gigstack.io/reference/getInvoicesErrors
GET /invoices/errors
Retrieve a paginated and filterable list of CFDI errors from the error matrix.
Use this endpoint to search for error codes, understand error causes, and find solutions.
**Query Options:**
- Filter by exact error code using `code` parameter
- Search across all fields using `q` parameter
- Filter by error type (invoice, receiver, sender, unknown)
- Paginate results with `limit` and `page` parameters
# List income invoices
Source: https://docs.gigstack.io/reference/getInvoicesIncome
GET /invoices/income
Retrieve a paginated list of income invoices with powerful filtering capabilities.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
**Filtering Options:**
- Filter by creation date using comparison operators
- Filter by `status`, `series`, `folio` and `idempotency_key` (exact match only)
- Filter by metadata fields using dot or underscore notation (e.g., `metadata.order_id` or `metadata_order_id`)
# Get income invoice
Source: https://docs.gigstack.io/reference/getInvoicesIncomeById
GET /invoices/income/{id}
Retrieve a specific income invoice by ID.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# List payment complement invoices
Source: https://docs.gigstack.io/reference/getInvoicesPayment
GET /invoices/payment
Retrieve a paginated list of payment complement invoices (CFDI type P - Complemento de Pago).
Payment complements are used for PPD (Pago en Parcialidades o Diferido) invoices to register partial or deferred payments.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# Get payment complement invoice
Source: https://docs.gigstack.io/reference/getInvoicesPaymentById
GET /invoices/payment/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve a specific payment complement invoice by ID (CFDI type P - Complemento de Pago).
Payment complements contain details about payments made against PPD invoices, including:
- Payment amounts and dates
- Payment method and form
- Related PPD invoices
- Tax calculations on payments
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Search invoices
Source: https://docs.gigstack.io/reference/getInvoicesSearch
GET /invoices/search
Full-text search across invoices using Typesense. Provides fast, typo-tolerant search capabilities.
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
**Search Capabilities:**
- Search across client name, email, invoice UUID, description, and metadata
- Typo-tolerant fuzzy matching
- Paginated results
**Requirements:**
- Typesense must be configured for your team
- The `q` (or `query`) parameter is required
# List transfer invoices
Source: https://docs.gigstack.io/reference/getInvoicesTransfer
GET /invoices/transfer
Retrieve a paginated list of transfer invoices (CFDI type T - Traslado with Carta Porte).
**gigstack Connect:** Access other teams' invoices using the `team` parameter.
# Get transfer invoice
Source: https://docs.gigstack.io/reference/getInvoicesTransferById
GET /invoices/transfer/{id}
Retrieve a specific transfer invoice by ID (CFDI type T - Traslado).
# Get payment
Source: https://docs.gigstack.io/reference/getPaymentsById
GET /payments/{id}
Retrieve a specific payment by ID.
**gigstack Connect:** Access other teams' payments using the `team` parameter.
# List support documents
Source: https://docs.gigstack.io/reference/getPaymentsByIdSupportDocuments
GET /payments/{id}/support-documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Retrieve all supporting documents attached to a payment.
**gigstack Connect:** View documents for other teams' payments using the `team` parameter.
Documents are returned sorted by creation date (newest first).
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Search payments
Source: https://docs.gigstack.io/reference/getPaymentsSearch
GET /payments/search
Full-text search across payments using Typesense. Provides fast, typo-tolerant search capabilities.
**gigstack Connect:** Access other teams' payments using the `team` parameter.
**Search Capabilities:**
- Search across client name, email, payment ID, description, and metadata
- Typo-tolerant fuzzy matching
- Filter search results by status, currency, or client ID
- Paginated results
**Requirements:**
- Typesense must be configured for your team
- The `q` (or `query`) parameter is required
# Get a platform payouts run
Source: https://docs.gigstack.io/reference/getPlatformPayoutRun
GET /platform-payouts/{id}
Returns the run: its status, plan totals and, once confirmed, the worker's progress. Poll it after
confirming until `status` is `completed` or `failed`; the worker runs every minute. Then read
`result`, not `status`, to know how the run went: `status: completed` only means the worker has
nothing left to try, while `result` tells `completed` (everything issued) from
`partially_completed` (some documents failed) and `failed` (nothing issued).
A run of another team, or of the other mode (a live run with a test key), answers `404`
`run_not_found`, never `403`.
# Get receipt
Source: https://docs.gigstack.io/reference/getReceiptsById
GET /receipts/{id}
Retrieve a specific receipt by ID.
**gigstack Connect:** View other teams' receipts using the `team` parameter.
# Search receipts
Source: https://docs.gigstack.io/reference/getReceiptsSearch
GET /receipts/search
Full-text search across receipts using Typesense. Provides fast, typo-tolerant search capabilities.
**gigstack Connect:** Access other teams' receipts using the `team` parameter.
**Search Capabilities:**
- Search across client name, email, receipt description, and metadata
- Typo-tolerant fuzzy matching
- Paginated results
**Requirements:**
- Typesense must be configured for your team
- The `q` (or `query`) parameter is required
# Get retention
Source: https://docs.gigstack.io/reference/getRetentionsById
GET /retentions/{id}
Retrieve a specific tax retention document by ID (UUID).
**gigstack Connect:** View other teams' retentions using the `team` parameter.
# Get retention files
Source: https://docs.gigstack.io/reference/getRetentionsByIdFiles
GET /retentions/{id}/files
Retrieve PDF and/or XML files for a stamped tax retention document.
Use `file_type` query parameter to request specific file types.
**gigstack Connect:** Access other teams' retention files using the `team` parameter.
# Get a SAT invoice
Source: https://docs.gigstack.io/reference/getSatInvoice
GET /invoices/sat/{uuid}
Returns a single invoice downloaded from SAT by its UUID.
# Check an RFC against the SAT lists
Source: https://docs.gigstack.io/reference/getSatListsCheckByRfc
GET /sat-lists/check/{rfc}
Checks whether an RFC appears on any of the SAT lists gigstack tracks, and returns every matching entry.
Use this before invoicing a counterparty: `is_risky` is `true` when the RFC appears on at least one list
flagged as risky (`Cancelados`, `Definitivos 69-B`, `Presuntos 69-B`, `No localizados`, `CSD sin efectos`, …),
and `risky_lists` names exactly which ones.
An RFC that appears on no list returns `200` with `found: false` — a clean RFC is not a `404`.
# Consult the SAT "Opinión del Cumplimiento" (32-D) for an RFC
Source: https://docs.gigstack.io/reference/getSatOpinion32dByRfc
GET /sat-lists/32d/{rfc}
Consults the SAT's public *Opinión del Cumplimiento de Obligaciones Fiscales* (Artículo 32-D) service
for an RFC and, when the SAT publishes one, returns a link to the constancia PDF.
### How to read the result — please read before building on this
The SAT's public service **only ever publishes positive opinions**, and only for taxpayers who
explicitly authorized public disclosure of their opinion. There are exactly two outcomes:
- `status: "positiva"` (`found: true`) — the SAT publishes a positive opinion for this RFC, and
`pdf_url` links to the constancia.
- `status: "no_autorizado"` (`found: false`) — the SAT publishes nothing for this RFC. **This means
the result is unknown.** Either the taxpayer never opted in to public disclosure, or no opinion is
published. It is **not** a negative opinion, it is **not** evidence of non-compliance, and it must
never be shown to a user as "opinión negativa", "incumplido", or anything equivalent. The only
correct reading is "the SAT does not publish an opinion for this RFC".
There is no third status: the SAT never exposes negative opinions through this service, so this
endpoint can never tell you that a taxpayer is non-compliant.
A failure reaching the SAT — network error, or the SAT changing its page — returns `500`. It is never
collapsed into `no_autorizado`, so `no_autorizado` always reflects a real answer from the SAT.
# Get service
Source: https://docs.gigstack.io/reference/getServicesById
GET /services/{id}
Retrieve a specific service by ID.
**gigstack Connect:** Access other teams' services using the `team` parameter.
# Get team integrations (not implemented)
Source: https://docs.gigstack.io/reference/getTeamIntegrations
GET /teams/integrations
**Not yet available.** The route is registered and reachable — it is declared before
`/teams/{id}` so it is no longer shadowed by the team-lookup route — but the handler is
still a stub that returns `501 Not Implemented` for every request. It never returns
integration data.
Do not build against this endpoint yet. This entry exists so the published surface
matches the deployed behavior.
# Get team
Source: https://docs.gigstack.io/reference/getTeamsById
GET /teams/{id}
Retrieve a specific team by ID.
**gigstack Connect:** Access other teams using the `team` parameter.
# Get team onboarding URL
Source: https://docs.gigstack.io/reference/getTeamsByIdOnboardingUrl
GET /teams/{id}/onboarding-url
Generate a secure onboarding URL for team setup and configuration.
**Important:** This endpoint is only available for gigstack Connect accounts (master teams).
## Use Cases
- Generate onboarding links for new teams
- Allow secure team configuration setup
- Enable embedded team management flows
**gigstack Connect:** Generate onboarding URLs for other teams using the `team` parameter.
# Get team series
Source: https://docs.gigstack.io/reference/getTeamsByIdSeries
GET /teams/{id}/series
Get series for a team.
**gigstack Connect:** Get series for other teams using the `team` parameter.
# Get user
Source: https://docs.gigstack.io/reference/getUsersById
GET /users/{id}
Retrieve a specific user by ID.
**gigstack Connect:** Access other teams' users using the `team` parameter.
# Get webhook
Source: https://docs.gigstack.io/reference/getWebhooksById
GET /webhooks/{id}
Retrieve details of a specific webhook by ID.
**gigstack Connect:** Access other teams' webhooks using the `team` parameter.
# Import CFDI XMLs the team already holds
Source: https://docs.gigstack.io/reference/importInvoiceXml
POST /invoices/import
Imports up to 50 stamped CFDI XMLs and files each one by the team's RFC:
- **Team is the issuer** → stored as an invoice (`GET /invoices/{id}`), with its XML.
- **Team is the receiver** → stored with the SAT received invoices (`GET /invoices/sat`), already imported, so Descarga Masiva will not download or bill it again.
- **Neither** → rejected; nothing is written.
Nothing is billed. A UUID the team already holds is reported as `already_exists`; one held by another account as `conflict`. Received nómina XMLs are rejected because they contain employee personal data. Status is assumed `Vigente`: an XML cannot show a later cancellation.
Send each file as `xml` (the XML text) or `content` (base64).
# Download XMLs for chosen CFDIs
Source: https://docs.gigstack.io/reference/importInvoicesDownload
POST /invoices/download/import
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
**This is the billed call.** Each XML costs $0.20 MXN, charged once per CFDI.
Takes UUIDs that are currently in the `metadata` stage — typically ones you found via a preview and `GET /invoices/sat?sync_state=metadata` — and queues their XML download.
**Cost confirmation.** The server always recomputes the cost; `confirm_cost_mxn` is only ever checked against it, never trusted. Send it and a mismatch returns **409** rather than charging a different amount than you were shown. Omit it and the call is allowed only up to 100 invoices; past that confirmation is required, so a large import cannot happen by accident.
Anything not importable is reported in `skipped` with a reason rather than failing the call: `not_found`, `wrong_team`, `already_imported`, `already_queued`, `is_nomina` (nómina XMLs carry employee PII and are never downloadable), `not_importable`.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Health check
Source: https://docs.gigstack.io/reference/invoicesHealth
GET /invoices/health
Liveness probe for the invoices module. Unauthenticated.
# Link document to an entity
Source: https://docs.gigstack.io/reference/linkDocument
POST /documents/{id}/link
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Attach the document to an invoice, payment, receipt or client. The link is appended to
the document's `linkedEntities` and the document id is added to the entity's
`satDocuments` array.
Linking the same document to the same entity twice returns `400`.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# List clients
Source: https://docs.gigstack.io/reference/listClients
GET /clients
Retrieve a paginated list of clients with powerful filtering capabilities.
**gigstack Connect:** Access other teams' clients using the `team` parameter.
**Filtering Options:**
- Filter by creation date using comparison operators
- Filter by metadata fields using dot or underscore notation (e.g., `metadata.external_id` or `metadata_external_id`)
# List documents
Source: https://docs.gigstack.io/reference/listDocuments
GET /documents
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
List the team's documents, newest first. Soft-deleted documents are excluded.
`entity_type` and `entity_id` filter on the document's links. `entity_id` is applied
client-side after the query, so it only narrows the page that was already fetched —
combine it with a larger `limit` if you expect sparse matches.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# List the items of an income invoice batch
Source: https://docs.gigstack.io/reference/listIncomeInvoiceBatchItems
GET /invoices/income/batch/{id}/items
One entry per **accepted** item, in request order (ascending `index`), with its status and, once issued,
its invoice. Items rejected up front are not listed; they are in the batch's `rejected`.
Cursor pagination: pass `data.next` back as `next` while `data.has_more` is `true`. The cursor is the last
`index` of the page, so pages never overlap or skip items while statuses change. Filter with `status`,
for example `status=failed` to list only what needs fixing.
# List recent preview and import jobs
Source: https://docs.gigstack.io/reference/listInvoicesDownloadJobs
GET /invoices/download/jobs
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
The last 20 jobs for your team, newest first, each with its progress and cost estimate.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# List payments
Source: https://docs.gigstack.io/reference/listPayments
GET /payments
Retrieve a paginated list of payments with powerful filtering capabilities.
**gigstack Connect:** Access other teams' payments using the `team` parameter.
**Filtering Options:**
- Filter by payment status, currency, amount
- Filter by client ID, email, tax ID (RFC), or name
- Filter by metadata fields using dot or underscore notation (e.g., `metadata.order_id` or `metadata_order_id`)
- Filter by creation date using comparison operators
# List a platform payouts run's movements
Source: https://docs.gigstack.io/reference/listPlatformPayoutMovements
GET /platform-payouts/{id}/movements
One entry per row of the movements file, in file order, with the state of its income invoice and
retention certificate. Use it to review exclusions before confirming, and to find failures after.
Cursor pagination: pass `data.next` back as `next` while `data.has_more` is `true`. The cursor is
stable while the worker updates statuses, so pages never overlap or skip rows.
Commission invoices (one per provider-month) are not listed here; the run only reports their counts.
# List receipts
Source: https://docs.gigstack.io/reference/listReceipts
GET /receipts
Retrieve a paginated list of receipts.
**gigstack Connect:** View other teams' receipts using the `team` parameter.
**Filters:** only `client_id`, `tax_id` and the `created[...]` range are applied. `status`, `valid_until` and
`metadata` are accepted but **ignored** — they do not narrow the results. Filter on those fields client-side.
# List retentions
Source: https://docs.gigstack.io/reference/listRetentions
GET /retentions
Retrieve a paginated list of tax retention documents (CFDI Retenciones 2.0).
**gigstack Connect:** View other teams' retentions using the `team` parameter.
# List SAT invoices
Source: https://docs.gigstack.io/reference/listSatInvoices
GET /invoices/sat
Returns invoices downloaded from SAT via Descarga Masiva. Supports filtering by direction,
status, invoice type, RFC, and date range. Uses cursor-based pagination.
Requires Descarga Masiva to be activated and at least one download request to have completed.
# List SAT lists and sync status
Source: https://docs.gigstack.io/reference/listSatLists
GET /sat-lists
Returns every SAT list definition tracked by gigstack, together with the metadata from its most recent sync.
gigstack mirrors the RFC lists the SAT publishes under **Artículo 69**, **Artículo 69-B** and **Artículo 69-B Bis**.
The lists are re-synced automatically every Sunday from the SAT's published CSVs.
Each list carries an `is_risky` flag. Risky lists (for example `Cancelados`, `Definitivos 69-B`, `No localizados`,
`CSD sin efectos`) are the ones that indicate a counterparty you should not invoice; the remaining lists are
informational only.
The `sync` object is `null` for a list that has never completed a sync.
# List services
Source: https://docs.gigstack.io/reference/listServices
GET /services
Retrieve a paginated list of services.
**gigstack Connect:** Access other teams' services using the `team` parameter.
# List teams
Source: https://docs.gigstack.io/reference/listTeams
GET /teams
Retrieve a paginated list of teams.
**gigstack Connect:** Access other teams using the `team` parameter.
# List users
Source: https://docs.gigstack.io/reference/listUsers
GET /users
Retrieve a paginated list of users.
**gigstack Connect:** Access other teams' users using the `team` parameter.
# List webhooks
Source: https://docs.gigstack.io/reference/listWebhooks
GET /webhooks
Retrieve all configured webhooks for your team.
**gigstack Connect:** Access other teams' webhooks using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/reference/paymentsHealth
GET /payments/health
Liveness probe for the payments module. Unauthenticated.
# Health check
Source: https://docs.gigstack.io/reference/platformPayoutsHealth
GET /platform-payouts/health
Liveness probe for the platform payouts module. Unauthenticated.
# Create transfer invoice (Carta Porte)
Source: https://docs.gigstack.io/reference/postInvoicesTransfer
POST /invoices/transfer
Stamp a CFDI type T (traslado) with the Carta Porte 3.1 complement, for moving goods by road
(autotransporte).
- The comprobante is fixed by the SAT: `Moneda` XXX, `Total` 0, `UsoCFDI` S01, no `MetodoPago`.
`currency`, `use`, `payment_method` and `payment_form` are ignored if sent.
- There are no `items`: the CFDI conceptos are built from `carta_porte.Mercancias.Mercancia`.
- `carta_porte` uses the SAT attribute names from CartaPorte31.xsd. Numbers may be sent as
numbers or numeric strings.
- At least one `Origen` and one `Destino`; every `Destino` needs `DistanciaRecorrida`.
`DistanciaRecorrida` is dropped from the `Origen`.
- `FiguraTransporte` is required (for `TipoFigura` 01, the operator, send `NumLicencia`).
- The series defaults to `T`.
- Only teams stamping through gigstack's own PAC (CSD uploaded) can use this endpoint.
Stamping is irreversible. Use a test API key to try it without fiscal effect.
# Health check
Source: https://docs.gigstack.io/reference/receiptsHealth
GET /receipts/health
Liveness probe for the receipts module. Unauthenticated.
# Reset user password
Source: https://docs.gigstack.io/reference/resetUserPassword
POST /users/reset-password/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Generate a Firebase password-reset link for the user and email it to them.
The user id goes in the **path** — the only registered route is
`POST /v2/users/reset-password/{id}`. There is no body-based variant, and the request
body is ignored entirely.
**gigstack Connect:** Reset passwords for other teams' users using the `team` parameter.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Retry XML download
Source: https://docs.gigstack.io/reference/retrySatXmlDownload
POST /invoices/sat/{uuid}/retry-xml
Manually retry the XML download for a received SAT invoice stuck in processing or error state.
# Trigger end-of-month global invoicing
Source: https://docs.gigstack.io/reference/runEndOfMonthInvoicing
POST /invoices/eom/run
Manually trigger the end-of-month (EOM) global invoicing process for your team, which
groups pending receipts into global invoices.
**Two hard preconditions:**
- The API key must be **livemode**. Test keys are rejected with `403`.
- The call must happen on the **last calendar day of the month** (America/Mexico_City).
Any other day is rejected with `400`.
- The call must happen **before 23:00 America/Mexico_City**. From 23:00 on, the automatic
end-of-month run takes over and manual calls are rejected with `400`.
The request body is ignored.
The response is returned as soon as the process is *triggered* — it does not wait for
the run to finish. A validation pass runs asynchronously afterwards; failures there are
logged server-side and are not reflected in this response.
# Health check
Source: https://docs.gigstack.io/reference/servicesHealth
GET /services/health
Liveness probe for the services module. Unauthenticated.
# Headless signup (API-only path)
Source: https://docs.gigstack.io/reference/signup
POST /auth/signup
Create a complete gigstack account end-to-end without the alphav2 web UI.
Designed for AI agents and partner integrations: a single call provisions
a Firebase Auth user, billing account, team, plan subscription, and returns
live + test API keys.
**Auth:** This endpoint authenticates with `X-Internal-API-Key` (partner-issued),
NOT a normal `Authorization: Bearer` API token. Bearer tokens are scoped to a
team — a brand-new caller has none yet.
**Idempotency:** `Idempotency-Key` header (UUID v4) is required. Retries with
the same key return the cached response and never re-create resources.
**Plan paths:**
- `plan_id: "free"` — direct Firestore write, no Stripe, instant activation.
- Any paid plan — uses `stripe.subscriptions.create` with the supplied
PaymentMethod token (off-session). No Checkout redirect. `billingAccount.haveAPIAccess`
is set synchronously so the API gate passes on the first call.
**RFC and FIEL:** not required at signup. Defaults to RFC genérico
(`XAXX010101000`). Customers can later upload their own via
`POST /v2/teams/{team_id}/sat-connection`. Clients, payments, services,
receipts, and webhooks all work without FIEL.
**API keys:** returned in the response body **once** — not retrievable later.
The caller must store them immediately.
# Stamp pending receipts
Source: https://docs.gigstack.io/reference/stampClientPendingReceipts
POST /clients/{id}/stamp-pending-receipts
Stamp the client's pending receipts into CFDI invoices.
**Batch cap:** at most **100** receipts are stamped per call. Receipts are processed
five at a time. If the client has more than 100 pending receipts, call the endpoint
repeatedly until `data.remaining` reaches `0`.
`data.remaining` is re-counted from Firestore *after* stamping, so it includes both
receipts beyond the 100-item cap and receipts that failed in this run (they stay
`pending`).
**Fiscal prerequisites.** Before stamping anything the handler checks that the client
has an RFC (`rfc`, falling back to `tax_id`), a legal name (`legal_name`, falling back
to `name`), `address.zip`, and `tax_system`. If any is missing it returns `400` with
`error.code: client_fiscal_data_incomplete` and stamps nothing. `email` and
`address.country` are not checked (`country` defaults to `MEX`).
**gigstack Connect:** Stamp other teams' client receipts using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/reference/teamsHealth
GET /teams/health
Liveness probe for the teams module. Unauthenticated.
# Unlink document from an entity
Source: https://docs.gigstack.io/reference/unlinkDocument
DELETE /documents/{id}/link
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Remove the link between the document and an entity. The document itself is not deleted.
This `DELETE` takes a **request body** identifying the entity — the same shape as the
link call.
If the entity no longer exists, the link is still removed from the document and the call
succeeds.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Update bulk-download sync period
Source: https://docs.gigstack.io/reference/updateBulkDownloadSyncPeriod
PUT /invoices/download/sync-period
Re-register the team with the bulk-download provider so it syncs invoices from the
earliest date the service allows.
**The request body is ignored.** `sync_start_date` is always computed server-side and is
never accepted as user input — you cannot ask for an arbitrary start date.
Requires a team RFC, a prior FIEL registration, stored FIEL credentials, and a phone
number on the stored FIEL record.
# Update client
Source: https://docs.gigstack.io/reference/updateClientsById
PUT /clients/{id}
Update an existing client.
**Pending receipts:** By default, after a successful update, if the client passes fiscal validation, all of the client's pending receipts are automatically invoiced using the new client data. Set `check_pending_receipts: false` in the body to skip this behavior. When the check runs, the response includes a `pending_receipts` summary.
**gigstack Connect:** Update other teams' clients using the `team` parameter.
# Update document
Source: https://docs.gigstack.io/reference/updateDocument
PATCH /documents/{id}
**Availability:** This operation has no configured public API gateway route and is not available through the documented base URL.
Update a document's metadata and compliance review state. Only fields explicitly
present in the body are written; omitted fields are left untouched.
The file itself (`fileUrl`, `storagePath`, `fileName`, `documentType`) is immutable —
those keys are not part of the update schema and are rejected as unknown.
This operation exists in Discovery, but a matching public API gateway route is not configured. It is not currently available through the documented base URL.
# Save schedule configuration
Source: https://docs.gigstack.io/reference/updateInvoicesDownloadSchedule
PUT /invoices/download/schedule
Create or update the daily scheduled download. The schedule runs once per day at the specified time (America/Mexico_City timezone) and downloads invoices from the last `days_back` days.
**Prerequisites:** FIEL must be uploaded and business must be registered. Returns `400` otherwise.
**Warning:** If you include `issued` in `download_types` and your team already has SAT invoicing (CSD) configured, the response includes a `warning` field noting that issued invoices already exist in the system.
# Update draft invoice
Source: https://docs.gigstack.io/reference/updateInvoicesDraftById
PUT /invoices/draft/{id}
Update an existing draft invoice with new data. Supports partial updates — only send the fields you want to change.
**gigstack Connect:** Update other teams' drafts using the `team` parameter.
# Update payment
Source: https://docs.gigstack.io/reference/updatePaymentsById
PUT /payments/{id}
Update a payment. The endpoint supports modifying the `description` of items, attaching `automation_type` to a payment that has no automations, and patching the embedded `client` (with the change propagated to the `/clients/{id}` master record). The body must include at least one of `items`, `automation_type` or `client`.
**gigstack Connect:** Update other teams' payments using the `team` parameter.
## What can be updated
- **Item description**: For each entry in `items`, the item is matched by `id` inside the payment and its `description` is replaced. Any other field on the item (taxes, discounts, quantity, unit_price, etc.) is rejected by validation. Item totals, taxes and `itemsAmounts` are not recalculated.
- **Automations**: `automation_type` is only accepted when the payment has no existing automations. If the payment already has automations, the request returns 400. The same enum values used by `POST /payments/register` apply (`pue_invoice`, `ppd_invoice_and_complement`, `none`).
- **Client**: The `client` object accepts a partial patch (`name`, `company`, `phone`, `email`, `bcc`, `metadata`, `legal_name`, `tax_id`, `use`, `tax_system`, `address`). The client `id` cannot be modified. Each provided field is written both to the `client` embedded in the payment and to the `/clients/{id}` master document via a partial merge. Fiscal/SAT validation is not re-run from this endpoint — call `PUT /clients/{id}` if full re-validation is needed.
## Trigger re-fire for already succeeded payments
When non-empty automations are added (i.e. `automation_type` is `pue_invoice` or `ppd_invoice_and_complement`) and the payment is already `succeeded`, the endpoint performs a second write that sets `status` to `succeeded_` so that the downstream automation trigger (which fires on transitions into `succeeded`) can re-fire on a subsequent flip back to `succeeded`.
## Allowed payment statuses
The endpoint accepts updates regardless of payment status (including `succeeded` and `cancelled`) so descriptions can be corrected after the fact. Note that this endpoint does not re-issue or modify any CFDI already linked to the payment.
# Update service
Source: https://docs.gigstack.io/reference/updateServicesById
PUT /services/{id}
Update an existing service.
**gigstack Connect:** Update other teams' services using the `team` parameter.
# Update team
Source: https://docs.gigstack.io/reference/updateTeamsById
PUT /teams/{id}
Update an existing team.
**gigstack Connect:** Update other teams using the `team` parameter.
# Update team series
Source: https://docs.gigstack.io/reference/updateTeamsByIdSeriesBySeriesId
PUT /teams/{id}/series/{seriesId}
Update a team series.
**gigstack Connect:** Update series for other teams using the `team` parameter.
# Update team settings
Source: https://docs.gigstack.io/reference/updateTeamsByIdSettings
PUT /teams/{id}/settings
Update team settings including defaults for invoicing, taxes, series, and email configurations.
**gigstack Connect:** Update settings for other teams using the `team` parameter.
## Team Settings Configuration
This endpoint allows you to configure various team-wide defaults and behaviors:
- **Invoice Settings:** Default descriptions, PDF notes, product keys
- **Tax Configuration:** Default taxes for MXN and USD currencies
- **Email Settings:** BCC recipients, email preferences
- **CFDI Configuration:** Default series, uses, product/unit keys
- **Automation:** Payment complement automation for PPD invoices
# Update user
Source: https://docs.gigstack.io/reference/updateUsersById
PUT /users/{id}
Update an existing user.
**gigstack Connect:** Update other teams' users using the `team` parameter.
# Update webhook
Source: https://docs.gigstack.io/reference/updateWebhooksById
PUT /webhooks/{id}
Update an existing webhook's configuration. All fields are optional.
**gigstack Connect:** Update webhooks for other teams using the `team` parameter.
# Health check
Source: https://docs.gigstack.io/reference/usersHealth
GET /users/health
Liveness probe for the users module. Unauthenticated.
# Health check
Source: https://docs.gigstack.io/reference/webhooksHealth
GET /webhooks/health
Liveness probe for the webhooks module. Unauthenticated.
# Responses, pagination, and retries
Source: https://docs.gigstack.io/responses
Read the response shape for the endpoint you call.
Most endpoints return `success`, `data`, and a `timestamp` in epoch milliseconds.
Errors commonly contain an `error` object with a stable `code` and a `message`.
Some endpoints use a different shape; the operation's API reference is the contract.
## Read every page
Cursor-based lists return `data`, `has_more`, and `next` at the top level. Send `next`
back as the next request's query parameter, preserving the other filters:
```bash theme={null}
curl --fail-with-body --get 'https://api.gigstack.io/v2/clients' \
-H "Authorization: Bearer $GIGSTACK_API_KEY" \
--data-urlencode 'limit=10' \
--data-urlencode 'next=CURSOR_FROM_PREVIOUS_RESPONSE'
```
Stop when `has_more` is false. The usual default limit is 10, with a maximum of 100.
Search and some metadata-filtered lists use page-based pagination instead; check the endpoint.
## Handle errors deliberately
| Status | Next action |
| - | - |
| `400` | Fix request fields using the response details. |
| `401` | Check the key and the `Bearer ` prefix. |
| `403` | Check team access, mode, role, and plan permissions. |
| `404` | Check the resource ID and the team and mode it belongs to. |
| `409` | Read the conflict before deciding whether to retry. |
| `429` | Identify the limit. Document credits and daily quotas may need action or a reset. |
| `500`, `503` | Retry reads with bounded exponential backoff; reconcile writes first. |
## Avoid duplicate writes
A timeout does not prove a write failed. Look up the result before repeating a creation,
stamping, payment, or refund request. Use an idempotency key only where the endpoint documents
one; gigstack does not promise a universal idempotency header.
For callbacks, use the [webhook guide](/guides/webhooks). Event formats, signatures, and retry
behavior differ between resource events and SAT synchronization events.