curl --request POST \
--url https://api.gigstack.io/v2/platform-payouts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--header 'Idempotency-Key: <idempotency-key>' \
--form 'movements_file=@example-file;type=text/csv' \
--form 'commissions_file=@example-file;type=text/csv'const form = new FormData();
form.append('movements_file', '<string>');
form.append('commissions_file', '<string>');
const options = {
method: 'POST',
headers: {'Idempotency-Key': '<idempotency-key>', Authorization: 'Bearer <token>'}
};
options.body = form;
fetch('https://api.gigstack.io/v2/platform-payouts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.gigstack.io/v2/platform-payouts"
files = {
"movements_file": ("example-file", open("example-file", "rb"), "text/csv"),
"commissions_file": ("example-file", open("example-file", "rb"), "text/csv")
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>"
}
response = requests.post(url, files=files, headers=headers)
print(response.text){
"success": true,
"data": {
"id": "batchrun_3f9a1c7e2b4d6f8a0c1e3a5b7d9f1a2c",
"status": "planning",
"result": null,
"livemode": true,
"team": "team_1234567890",
"created_at": 1788220800000,
"plan_ready_at": null,
"confirmed_at": null,
"completed_at": null,
"files": {
"movements": "movimientos-agosto-2026.csv",
"commissions": "comisiones-agosto-2026.xlsx"
},
"months": [],
"total_movements": 0,
"included_count": 0,
"excluded_count": 0,
"planned_documents": {
"income": 0,
"certificate": 0,
"commission": 0
},
"exclusion_summary": {},
"exclusion_code_summary": {},
"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": 1788220802000
}Upload files and plan a platform payouts run
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.
curl --request POST \
--url https://api.gigstack.io/v2/platform-payouts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--header 'Idempotency-Key: <idempotency-key>' \
--form 'movements_file=@example-file;type=text/csv' \
--form 'commissions_file=@example-file;type=text/csv'const form = new FormData();
form.append('movements_file', '<string>');
form.append('commissions_file', '<string>');
const options = {
method: 'POST',
headers: {'Idempotency-Key': '<idempotency-key>', Authorization: 'Bearer <token>'}
};
options.body = form;
fetch('https://api.gigstack.io/v2/platform-payouts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.gigstack.io/v2/platform-payouts"
files = {
"movements_file": ("example-file", open("example-file", "rb"), "text/csv"),
"commissions_file": ("example-file", open("example-file", "rb"), "text/csv")
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>"
}
response = requests.post(url, files=files, headers=headers)
print(response.text){
"success": true,
"data": {
"id": "batchrun_3f9a1c7e2b4d6f8a0c1e3a5b7d9f1a2c",
"status": "planning",
"result": null,
"livemode": true,
"team": "team_1234567890",
"created_at": 1788220800000,
"plan_ready_at": null,
"confirmed_at": null,
"completed_at": null,
"files": {
"movements": "movimientos-agosto-2026.csv",
"commissions": "comisiones-agosto-2026.xlsx"
},
"months": [],
"total_movements": 0,
"included_count": 0,
"excluded_count": 0,
"planned_documents": {
"income": 0,
"certificate": 0,
"commission": 0
},
"exclusion_summary": {},
"exclusion_code_summary": {},
"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": 1788220802000
}Authorizations
Authentication Method: HTTP Bearer token.
The runtime requires the literal Bearer prefix — a bare token in the
Authorization header is rejected with 401 unauthorized.
Header Format: Authorization: Bearer YOUR_API_KEY
Your API key is a JWT. Live keys operate on live data (livemode: true);
test keys operate on isolated test data (livemode: false).
Get your key at: app.gigstack.pro/settings?tab=api
Errors: credential failures are answered by the authentication layer with a raw
{ "message": … } body, not the standardized envelope — 401 for a missing, malformed or
expired token, 403 for a revoked key or a plan without API access. See the Unauthorized
and AuthForbidden responses.
Headers
Your identifier for this upload, 8-128 characters of A-Z a-z 0-9 . _ : -. Checked before the
files are read. Reusing it with the same files returns the run it first created; reusing it with
different files is 409 idempotency_key_reused.
8 - 128^[A-Za-z0-9._:-]{8,128}$Body
The two files a run is built from. Each is read by its extension (the MIME type is ignored):
.csv / .txt as comma-separated text, .xlsx / .xls / .xlsm as a workbook, of which only
the first sheet is read. The first row is the header. Headers are matched ignoring case, accents
and repeated spaces; extra columns are ignored.
One row per payout to a provider. Max 5 MB, max 50,000 rows. Required columns:
ID del proveedor, Nombre del proveedor, Correo electrónico, RFC, Fecha del movimiento
(YYYY-MM-DD or DD/MM/YYYY), Tipo de movimiento (free text such as Pago semanal or
Servicio), Subtotal (MXN, at least 0.01). Also accepted: Provider ID or Driver ID for
the id; Nombre del conductor, Razón social or Nombre for the name (the more specific one
wins when several are present); Correo or Email for the e-mail.
One row per provider per month with the platform commission. Max 5 MB, max 50,000 rows.
Required columns: the same provider columns as the movements file (ID del proveedor,
Nombre del proveedor, Correo electrónico, RFC, with the same alternatives), Mes
(YYYY-MM) and Comisión (MXN). Comisión Total and a few legacy export spellings are also
accepted for the amount.
Response
Retry of an earlier request with the same Idempotency-Key and the same file contents (or a
run created before file fingerprints existed): the run it created, in whatever status it is
now. Nothing is planned again. planning means the first request is still working; poll
GET /platform-payouts/{id}.
Standardized success envelope emitted by sendSuccessResponse.
true true
A platform payouts run: the files it was planned from, the plan totals, and the stamping progress. Amounts are MXN in pesos (not cents). Timestamps are epoch milliseconds.
Show child attributes
Show child attributes
Server time in epoch milliseconds (Luxon.now().toMillis()).
1767225600000
Human-readable summary. Present only when the handler supplies one.
"Operation completed successfully"