curl --request POST \
--url https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"password": "string"
}
'import requests
url = "https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential"
payload = { "password": "string" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({password: 'string'})
};
fetch('https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'password' => 'string'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential"
payload := strings.NewReader("{\n \"password\": \"string\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"password\": \"string\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"password\": \"string\"\n}"
response = http.request(request)
puts response.read_body{
"object": "sat_credential",
"fiscal_profile_id": "fp_2P9K3sample",
"rfc": "XAXX010101000",
"status": "pending",
"expires_at": "2026-07-10T18:25:43Z",
"remediation": "string",
"livemode": false
}{
"type": "https://errors.invoiceapi.mx/auth_unauthorized",
"title": "Authentication required",
"status": 401,
"code": "auth.unauthorized",
"detail": "Missing or invalid API key.",
"remediation": "Send `Authorization: Bearer sk_test_...` (or sk_live_...) with a valid API key.",
"doc_url": "/docs/errors#auth-unauthorized",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/resource_not_found",
"title": "Resource not found",
"status": 404,
"code": "resource.not_found",
"detail": "No such id in this org + mode.",
"remediation": "Verify the id and that the key mode (test/live) matches the id.",
"doc_url": "/docs/errors#resource-not_found",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/fiscal_profile_in_use",
"title": "Fiscal profile is in use",
"status": 409,
"code": "fiscal_profile.in_use",
"detail": "The profile is referenced by issued invoices.",
"remediation": "Profiles with invoices are retained; rotate the CSD instead of deleting.",
"doc_url": "/docs/errors#fiscal_profile-in_use",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/fiscal_profile_invalid_csd",
"title": "Invalid CSD certificate or password",
"status": 422,
"code": "fiscal_profile.invalid_csd",
"detail": "The CSD .cer/.key pair could not be opened.",
"remediation": "Verify cer_base64 is the .cer, key_base64 is the .key, and the password.",
"doc_url": "/docs/errors#fiscal_profile-invalid_csd",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/rate_limit_exceeded",
"title": "Rate limit exceeded",
"status": 429,
"code": "rate_limit.exceeded",
"detail": "Too many requests for this key + request class (reads and writes are limited separately).",
"remediation": "Back off and retry after the number of seconds in the Retry-After header; batch work or lower your request rate.",
"doc_url": "/docs/errors#rate_limit-exceeded",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/internal_error",
"title": "Internal server error",
"status": 500,
"code": "internal.error",
"detail": "An unexpected error occurred.",
"remediation": "Retry later; contact support with the request_id if it persists.",
"doc_url": "/docs/errors#internal-error",
"request_id": "req_2P9K3sample"
}Save a SAT credential (CIEC or FIEL)
Save the taxpayer’s SAT password (CIEC), or their FIEL (e.firma), for this profile’s RFC. Send either password or fiel. The credential is checked against SAT asynchronously, so a live response starts as pending; wait for the fiscal_profile.sat_credential_verified webhook or poll GET before relying on it. Saving again replaces and revokes the previous credential. Credentials expire after at most 90 days; a fiscal_profile.sat_credential_expiring webhook fires 14 days ahead. Test-mode and sandbox profiles are simulated and verified immediately; nothing leaves this API.
curl --request POST \
--url https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"password": "string"
}
'import requests
url = "https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential"
payload = { "password": "string" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({password: 'string'})
};
fetch('https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'password' => 'string'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential"
payload := strings.NewReader("{\n \"password\": \"string\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"password\": \"string\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.newinvoice.dev/v1/fiscal_profiles/{id}/sat_credential")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"password\": \"string\"\n}"
response = http.request(request)
puts response.read_body{
"object": "sat_credential",
"fiscal_profile_id": "fp_2P9K3sample",
"rfc": "XAXX010101000",
"status": "pending",
"expires_at": "2026-07-10T18:25:43Z",
"remediation": "string",
"livemode": false
}{
"type": "https://errors.invoiceapi.mx/auth_unauthorized",
"title": "Authentication required",
"status": 401,
"code": "auth.unauthorized",
"detail": "Missing or invalid API key.",
"remediation": "Send `Authorization: Bearer sk_test_...` (or sk_live_...) with a valid API key.",
"doc_url": "/docs/errors#auth-unauthorized",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/resource_not_found",
"title": "Resource not found",
"status": 404,
"code": "resource.not_found",
"detail": "No such id in this org + mode.",
"remediation": "Verify the id and that the key mode (test/live) matches the id.",
"doc_url": "/docs/errors#resource-not_found",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/fiscal_profile_in_use",
"title": "Fiscal profile is in use",
"status": 409,
"code": "fiscal_profile.in_use",
"detail": "The profile is referenced by issued invoices.",
"remediation": "Profiles with invoices are retained; rotate the CSD instead of deleting.",
"doc_url": "/docs/errors#fiscal_profile-in_use",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/fiscal_profile_invalid_csd",
"title": "Invalid CSD certificate or password",
"status": 422,
"code": "fiscal_profile.invalid_csd",
"detail": "The CSD .cer/.key pair could not be opened.",
"remediation": "Verify cer_base64 is the .cer, key_base64 is the .key, and the password.",
"doc_url": "/docs/errors#fiscal_profile-invalid_csd",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/rate_limit_exceeded",
"title": "Rate limit exceeded",
"status": 429,
"code": "rate_limit.exceeded",
"detail": "Too many requests for this key + request class (reads and writes are limited separately).",
"remediation": "Back off and retry after the number of seconds in the Retry-After header; batch work or lower your request rate.",
"doc_url": "/docs/errors#rate_limit-exceeded",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/internal_error",
"title": "Internal server error",
"status": 500,
"code": "internal.error",
"detail": "An unexpected error occurred.",
"remediation": "Retry later; contact support with the request_id if it persists.",
"doc_url": "/docs/errors#internal-error",
"request_id": "req_2P9K3sample"
}Authorizations
API key: Authorization: Bearer sk_test_... or sk_live_....
Headers
Pin a dated API release (Stripe-style). Omit to use the current version. An unknown value returns 400 request.invalid_version. Echoed back on every response.
"2026-07-10"
Connect: act on behalf of one of your connected accounts (its acct_…/org_… id). Omit to act as your own organization. Not honored on /v1/api_keys.
"org_2P9connectedacct"
Safely retry any POST. A successful (2xx) response is stored for 24h and replayed byte-for-byte for identical retries (the replay adds an idempotent-replayed: true header). A non-2xx releases the key, so you can fix the cause and retry with the same key; pac.stamp_outcome_unknown, pac.auth_failed and download.request_failed are the exceptions and replay like a success. Reusing the key of a stored response with a different body is 409 idempotency.key_reuse; an in-flight duplicate is 409 idempotency.key_processing.
"a1b2c3d4-e5f6-4789-8abc-1234567890ab"
Path Parameters
The fiscal profile id, e.g. "fp_01H...".
Body
- Option 1
- Option 2
The taxpayer's SAT password (CIEC / contraseña) for the profile's RFC. Used once to save the credential and never stored by this API, in any form.
1 - 256Response
Default Response
Always "sat_credential".
sat_credential The fiscal profile this credential belongs to.
The RFC the credential authenticates.
pending: the SAT login check is running. verified: SAT accepted the password at the time of the check. rejected: SAT explicitly refused it. inconclusive: the check could not tell (for example an outage or a CAPTCHA problem). expired: the saved credential has passed expires_at.
pending, verified, rejected, inconclusive, expired When the saved credential expires, ISO-8601 UTC.
What to do next when status is rejected, inconclusive or expired; null otherwise. Written for humans and agents.
False for test-mode (simulated) credentials.