EIN Filing Workflow
The EIN (Employer Identification Number) filing workflow obtains a federal tax identification number for an existing business entity. EIN filings are not jurisdiction-specific: the order carries only an entity type and the filing type
EIN.
How the EIN workflow works
You complete and sign IRS Form SS-4 yourself, then submit it to SingleFile as a PDF together with a short set of structured fields (screening questions, the entity's primary address, a company phone number, a primary contact, an authorization acknowledgement, and the signer's name and title). SingleFile does not build the SS-4 from structured data. Our filing team uses your uploaded SS-4 to obtain the EIN from the IRS, attaches the IRS confirmation letter to the entity as a document, and completes the order. Webhooks tell you when that happens.
The authoritative list of fields is the schema returned by the Schema API (Step 2). Build your payload from that response rather than from this page.
Include
signer_name(conditional, and not yet in the schema). When the send-for-signature feature is enabled for your account, which is the default, every EIN order must carry a top-level string field namedsigner_nameholding the full name of the person who signed or will sign the SS-4. The rule applies whether or not the SS-4 is already signed; thesignerobject with an email is only needed when you ask SingleFile to collect the signature. Accounts with the feature switched off are not subject to this requirement. The field is not listed in the schema yet, and until a fix ships a missingsigner_namefails the preview and submit calls with an HTTP 500UNKNOWN_ERRORinstead of a 400. Send it alongsidesigner_name_and_title; it will keep working after the fix.
Prerequisites
- Valid API access token
- An existing organization and entity (EIN filings require a formed entity)
- A completed, signed IRS Form SS-4 as a PDF (or an unsigned one if you want SingleFile to collect the signature, see Step 3)
- The entity's primary address, a US company telephone number, and a primary contact
All examples use the demo environment, https://api.demo.singlefile.ai. Production is https://api.app.singlefile.io. Demo accepts orders and reports status but does not fulfil them.
Step 1: Find the Entity
EIN filings require an existing formed business entity. List the organization's entities and pick the one you need by name. Keep its id, which is a UUID; numeric dashboard ids are not accepted.
ORGANIZATION_ID="your_organization_uuid"
ACCESS_TOKEN="your_bearer_token_here"
curl -X GET "https://api.demo.singlefile.ai/external-api/v1/organizations/${ORGANIZATION_ID}/entities" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"import requests
BASE_URL = "https://api.demo.singlefile.ai/external-api/v1"
def get_existing_entity(organization_id, entity_name, access_token):
response = requests.get(
f"{BASE_URL}/organizations/{organization_id}/entities",
headers={"Authorization": f"Bearer {access_token}"},
)
response.raise_for_status()
for entity in response.json()["data"]:
if entity["name"] == entity_name:
return entity["id"]
raise Exception(f"Entity '{entity_name}' not found. EIN filings require a formed business entity.")
ACCESS_TOKEN = "your_bearer_token_here"
entity_id = get_existing_entity("your_organization_uuid", "Acme AI Solutions LLC", ACCESS_TOKEN)const BASE_URL = "https://api.demo.singlefile.ai/external-api/v1";
async function getExistingEntity(organizationId, entityName, accessToken) {
const response = await fetch(`${BASE_URL}/organizations/${organizationId}/entities`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const { data } = await response.json();
const entity = data.find((e) => e.name === entityName);
if (entity) return entity.id;
throw new Error(`Entity '${entityName}' not found. EIN filings require a formed business entity.`);
}
const ACCESS_TOKEN = "your_bearer_token_here";
const entityId = await getExistingEntity("your_organization_uuid", "Acme AI Solutions LLC", ACCESS_TOKEN);package main
import (
"encoding/json"
"fmt"
"net/http"
)
const baseURL = "https://api.demo.singlefile.ai/external-api/v1"
func getExistingEntity(organizationID, entityName, accessToken string) (string, error) {
url := fmt.Sprintf("%s/organizations/%s/entities", baseURL, organizationID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
resp, err := http.DefaultClient.Do(req)
if err != nil { return "", err }
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("request failed: %d", resp.StatusCode)
}
var result struct {
Data []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return "", err }
for _, e := range result.Data {
if e.Name == entityName { return e.ID, nil }
}
return "", fmt.Errorf("entity '%s' not found", entityName)
}
func main() {
entityID, err := getExistingEntity("your_organization_uuid", "Acme AI Solutions LLC", "your_bearer_token_here")
if err != nil { panic(err) }
_ = entityID
}using System.Net.Http;
using System.Text.Json;
const string BaseUrl = "https://api.demo.singlefile.ai/external-api/v1";
var organizationId = "your_organization_uuid";
var entityName = "Acme AI Solutions LLC";
var accessToken = "your_bearer_token_here";
var entityId = await GetExistingEntity(organizationId, entityName, accessToken);
async Task<string> GetExistingEntity(string orgId, string name, string token)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var response = await client.GetAsync($"{BaseUrl}/organizations/{orgId}/entities");
if (!response.IsSuccessStatusCode) throw new Exception($"Request failed: {response.StatusCode}");
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
foreach (var entity in doc.RootElement.GetProperty("data").EnumerateArray())
if (entity.GetProperty("name").GetString() == name)
return entity.GetProperty("id").GetString()!;
throw new Exception($"Entity '{name}' not found.");
}Step 2: Retrieve the EIN Filing Schema
The schema is the contract for the order payload: it lists every accepted field, which ones are required, and their validation rules. EIN schemas do not take a jurisdiction.
| Parameter | Description | Values |
|---|---|---|
entity_type | Type of business entity | llc, lp, corporation |
filing_type | Always ein | ein |
purpose | Purpose of EIN application | new_business |
ENTITY_TYPE="llc"
curl -X GET "https://api.demo.singlefile.ai/external-api/v1/schemas?entity_type=${ENTITY_TYPE}&filing_type=ein&purpose=new_business" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"def get_ein_schema(entity_type, access_token):
response = requests.get(
f"{BASE_URL}/schemas",
params={"entity_type": entity_type, "filing_type": "ein", "purpose": "new_business"},
headers={"Authorization": f"Bearer {access_token}"},
)
if response.status_code == 404:
raise Exception(f"EIN schema not found for {entity_type}")
response.raise_for_status()
return response.json()["data"]["schema"]
ein_schema = get_ein_schema("llc", ACCESS_TOKEN)
print(ein_schema["required"])async function getEinSchema(entityType, accessToken) {
const params = new URLSearchParams({ entity_type: entityType, filing_type: "ein", purpose: "new_business" });
const res = await fetch(`${BASE_URL}/schemas?${params}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (res.status === 404) throw new Error(`EIN schema not found for ${entityType}`);
if (!res.ok) throw new Error(`Failed to retrieve EIN schema: ${res.status}`);
return (await res.json()).data.schema;
}
const einSchema = await getEinSchema("llc", ACCESS_TOKEN);
console.log(einSchema.required);func getEinSchema(entityType, accessToken string) (map[string]interface{}, error) {
url := fmt.Sprintf("%s/schemas?entity_type=%s&filing_type=ein&purpose=new_business", baseURL, entityType)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer resp.Body.Close()
if resp.StatusCode == 404 {
return nil, fmt.Errorf("EIN schema not found for %s", entityType)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to retrieve EIN schema: %d", resp.StatusCode)
}
var result struct {
Data struct {
Schema map[string]interface{} `json:"schema"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err }
return result.Data.Schema, nil
}async Task<JsonElement> GetEinSchema(string entityType, string token)
{
var url = $"{BaseUrl}/schemas?entity_type={entityType}&filing_type=ein&purpose=new_business";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var res = await client.GetAsync(url);
if (res.StatusCode == System.Net.HttpStatusCode.NotFound)
throw new Exception($"EIN schema not found for {entityType}");
res.EnsureSuccessStatusCode();
var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
return doc.RootElement.GetProperty("data").GetProperty("schema").Clone();
}Step 3: Build the EIN Order Payload
The payload is the signed SS-4 plus the structured fields below. Field names and requirements come from the schema in Step 2; the table summarizes the LLC schema at the time of writing.
| Field | Description | Required |
|---|---|---|
filing_type | Always "EIN" | Yes |
entity_type | llc, lp or corporation | Yes |
entity | { "id": "<entity uuid>" } from Step 1 | Yes |
variant | Service level. Use "standard" | Yes |
document | The completed SS-4 PDF: { "filename", "content_type", "content" } with content base64-encoded | Yes |
ss4_signature_status | "signed" if the uploaded SS-4 is signed, "unsigned_send_for_signature" to have SingleFile collect the signature | Yes |
signer | { "first_name", "last_name", "email" } of the person to sign. Required when ss4_signature_status is "unsigned_send_for_signature" | Conditional |
signer_name_and_title | { "first_name", "last_name", "title", "phone" } of the person signing the application | Recommended |
signer_name | Full name of the signer as a plain string. Required whenever send-for-signature is enabled for your account (the default), even for an already-signed SS-4 (see the callout above) | Conditional, currently yes |
needs_form_720 | Does the business need to file Form 720 (Quarterly Federal Excise Tax Return)? "yes" or "no" | Yes |
owns_highway_motor_vehicle | Does it own a highway motor vehicle with taxable gross weight of 55,000 lb or more? "yes" or "no" | Yes |
expects_w2_employees | Will it have employees receiving Forms W-2 in the next 12 months? "yes" or "no" | Yes |
involves_gambling | Does it involve gambling or wagering? "yes" or "no" | Yes |
sells_alcohol_tobacco_firearms | Does it sell or manufacture alcohol, tobacco or firearms? "yes" or "no" | Yes |
primary_address | Physical address: street_address, city, state (2-letter), postal_code, country ("US") | Yes |
mailing_address | Same shape as primary_address, if different | No |
company_telephone | US phone number, required by the IRS | Yes |
primary_contact | { "first_name", "last_name", "email" }, optional telephone_number (10 digits) | Yes |
authorization_ack | true: the entity authorizes SingleFile to apply for and receive the EIN on its behalf | Yes |
comments | Free-text note for the filing team | No |
A complete payload:
{
"filing_type": "EIN",
"entity_type": "llc",
"entity": { "id": "e0190d40-50cd-4601-8517-3693725122fd" },
"variant": "standard",
"document": {
"filename": "ss4.pdf",
"content_type": "application/pdf",
"content": "JVBERi0xLjQK...base64 of the signed SS-4..."
},
"ss4_signature_status": "signed",
"signer_name_and_title": {
"first_name": "Jane",
"last_name": "Smith",
"title": "Managing Member",
"phone": "3025550123"
},
"signer_name": "Jane Smith",
"needs_form_720": "no",
"owns_highway_motor_vehicle": "no",
"expects_w2_employees": "yes",
"involves_gambling": "no",
"sells_alcohol_tobacco_firearms": "no",
"primary_address": {
"street_address": "123 Innovation Drive, Suite 100",
"city": "Wilmington",
"state": "DE",
"postal_code": "19801",
"country": "US"
},
"company_telephone": "3025550123",
"primary_contact": {
"first_name": "Jane",
"last_name": "Smith",
"email": "[email protected]"
},
"authorization_ack": true
}# Save the JSON above as ein_payload.json, replacing document.content with your
# base64-encoded SS-4, for example:
# base64 -i ss4.pdf | tr -d '\n'
# Steps 4 and 5 send it with -d @ein_payload.jsonimport base64
def create_ein_payload(entity_id, entity_type, ss4_pdf_path):
with open(ss4_pdf_path, "rb") as f:
ss4_base64 = base64.b64encode(f.read()).decode("ascii")
return {
"filing_type": "EIN",
"entity_type": entity_type,
"entity": {"id": entity_id},
"variant": "standard",
"document": {
"filename": "ss4.pdf",
"content_type": "application/pdf",
"content": ss4_base64,
},
"ss4_signature_status": "signed",
"signer_name_and_title": {
"first_name": "Jane",
"last_name": "Smith",
"title": "Managing Member",
"phone": "3025550123",
},
"signer_name": "Jane Smith",
"needs_form_720": "no",
"owns_highway_motor_vehicle": "no",
"expects_w2_employees": "yes",
"involves_gambling": "no",
"sells_alcohol_tobacco_firearms": "no",
"primary_address": {
"street_address": "123 Innovation Drive, Suite 100",
"city": "Wilmington",
"state": "DE",
"postal_code": "19801",
"country": "US",
},
"company_telephone": "3025550123",
"primary_contact": {
"first_name": "Jane",
"last_name": "Smith",
"email": "[email protected]",
},
"authorization_ack": True,
}
ein_payload = create_ein_payload(entity_id, "llc", "ss4.pdf")import { readFile } from "node:fs/promises";
async function createEinPayload(entityId, entityType, ss4PdfPath) {
const ss4Base64 = (await readFile(ss4PdfPath)).toString("base64");
return {
filing_type: "EIN",
entity_type: entityType,
entity: { id: entityId },
variant: "standard",
document: {
filename: "ss4.pdf",
content_type: "application/pdf",
content: ss4Base64,
},
ss4_signature_status: "signed",
signer_name_and_title: {
first_name: "Jane",
last_name: "Smith",
title: "Managing Member",
phone: "3025550123",
},
signer_name: "Jane Smith",
needs_form_720: "no",
owns_highway_motor_vehicle: "no",
expects_w2_employees: "yes",
involves_gambling: "no",
sells_alcohol_tobacco_firearms: "no",
primary_address: {
street_address: "123 Innovation Drive, Suite 100",
city: "Wilmington",
state: "DE",
postal_code: "19801",
country: "US",
},
company_telephone: "3025550123",
primary_contact: {
first_name: "Jane",
last_name: "Smith",
email: "[email protected]",
},
authorization_ack: true,
};
}
const einPayload = await createEinPayload(entityId, "llc", "ss4.pdf");import (
"encoding/base64"
"os"
)
func createEinPayload(entityID, entityType, ss4PdfPath string) (map[string]interface{}, error) {
pdf, err := os.ReadFile(ss4PdfPath)
if err != nil { return nil, err }
return map[string]interface{}{
"filing_type": "EIN",
"entity_type": entityType,
"entity": map[string]string{"id": entityID},
"variant": "standard",
"document": map[string]string{
"filename": "ss4.pdf",
"content_type": "application/pdf",
"content": base64.StdEncoding.EncodeToString(pdf),
},
"ss4_signature_status": "signed",
"signer_name_and_title": map[string]string{
"first_name": "Jane", "last_name": "Smith", "title": "Managing Member", "phone": "3025550123",
},
"signer_name": "Jane Smith",
"needs_form_720": "no",
"owns_highway_motor_vehicle": "no",
"expects_w2_employees": "yes",
"involves_gambling": "no",
"sells_alcohol_tobacco_firearms": "no",
"primary_address": map[string]string{
"street_address": "123 Innovation Drive, Suite 100",
"city": "Wilmington", "state": "DE", "postal_code": "19801", "country": "US",
},
"company_telephone": "3025550123",
"primary_contact": map[string]string{
"first_name": "Jane", "last_name": "Smith", "email": "[email protected]",
},
"authorization_ack": true,
}, nil
}Dictionary<string, object> CreateEinPayload(string entityId, string entityType, string ss4PdfPath)
{
var ss4Base64 = Convert.ToBase64String(File.ReadAllBytes(ss4PdfPath));
return new Dictionary<string, object> {
["filing_type"] = "EIN",
["entity_type"] = entityType,
["entity"] = new Dictionary<string, string> { ["id"] = entityId },
["variant"] = "standard",
["document"] = new Dictionary<string, string> {
["filename"] = "ss4.pdf", ["content_type"] = "application/pdf", ["content"] = ss4Base64
},
["ss4_signature_status"] = "signed",
["signer_name_and_title"] = new Dictionary<string, string> {
["first_name"] = "Jane", ["last_name"] = "Smith", ["title"] = "Managing Member", ["phone"] = "3025550123"
},
["signer_name"] = "Jane Smith",
["needs_form_720"] = "no",
["owns_highway_motor_vehicle"] = "no",
["expects_w2_employees"] = "yes",
["involves_gambling"] = "no",
["sells_alcohol_tobacco_firearms"] = "no",
["primary_address"] = new Dictionary<string, string> {
["street_address"] = "123 Innovation Drive, Suite 100", ["city"] = "Wilmington",
["state"] = "DE", ["postal_code"] = "19801", ["country"] = "US"
},
["company_telephone"] = "3025550123",
["primary_contact"] = new Dictionary<string, string> {
["first_name"] = "Jane", ["last_name"] = "Smith", ["email"] = "[email protected]"
},
["authorization_ack"] = true
};
}Having SingleFile collect the signature
If the SS-4 is not yet signed, set ss4_signature_status to "unsigned_send_for_signature" and add a signer object with the signer's first_name, last_name and email. SingleFile sends the signature request after the order is placed; you receive signature.requested, then signature.completed, signature.declined or signature.expired webhooks as it progresses. The filing does not start until the signed SS-4 is returned.
Step 4: Preview the Order
Preview validates the payload and returns the priced line items without creating an order or storing the document. A 200 response means the payload is accepted; a 400 lists the failing fields.
curl -X POST "https://api.demo.singlefile.ai/external-api/v1/orders?preview=true" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d @ein_payload.jsondef preview_ein_application(payload, access_token):
response = requests.post(
f"{BASE_URL}/orders",
json=payload,
params={"preview": "true"},
headers={"Authorization": f"Bearer {access_token}"},
)
if response.status_code == 200:
data = response.json()["data"]
print("EIN preview accepted. Total:", data["total_amount"])
return data
error = response.json().get("error", {})
print("Preview failed:", error.get("message", "Unknown error"), error.get("errors", ""))
return None
if not preview_ein_application(ein_payload, ACCESS_TOKEN):
raise Exception("EIN filing preview failed")async function previewEinApplication(payload, accessToken) {
const res = await fetch(`${BASE_URL}/orders?preview=true`, {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (res.ok) {
const { data } = await res.json();
console.log("EIN preview accepted. Total:", data.total_amount);
return data;
}
const err = await res.json().catch(() => ({}));
console.log("Preview failed:", err?.error?.message ?? "Unknown error", err?.error?.errors ?? "");
return null;
}
if (!(await previewEinApplication(einPayload, ACCESS_TOKEN))) throw new Error("EIN filing preview failed");func previewEinApplication(payload map[string]interface{}, accessToken string) bool {
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseURL+"/orders?preview=true", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { return false }
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
fmt.Println("EIN preview accepted")
return true
}
var errResp struct { Error struct { Message string `json:"message"` } `json:"error"` }
json.NewDecoder(resp.Body).Decode(&errResp)
fmt.Println("Preview failed:", errResp.Error.Message)
return false
}async Task<bool> PreviewEinApplication(object payload, string token)
{
var json = JsonSerializer.Serialize(payload);
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await client.PostAsync($"{BaseUrl}/orders?preview=true", content);
if (res.IsSuccessStatusCode) { Console.WriteLine("EIN preview accepted"); return true; }
var err = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
var msg = err.RootElement.TryGetProperty("error", out var e) ? e.GetProperty("message").GetString() : "Unknown error";
Console.WriteLine($"Preview failed: {msg}");
return false;
}Step 5: Submit the Order
Submit the same payload without the preview parameter. A 201 response returns the order, including its order_id. The SS-4 is stored as a document on the entity at this point.
curl -X POST "https://api.demo.singlefile.ai/external-api/v1/orders" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d @ein_payload.jsondef submit_ein_application(payload, access_token):
response = requests.post(
f"{BASE_URL}/orders",
json=payload,
headers={"Authorization": f"Bearer {access_token}"},
)
if response.status_code == 201:
order = response.json()["data"]
print(f"EIN filing submitted! Order ID: {order['order_id']}")
return order
print("Submission failed:", response.json().get("error", {}).get("message", "Unknown error"))
return None
submitted_order = submit_ein_application(ein_payload, ACCESS_TOKEN)async function submitEinApplication(payload, accessToken) {
const res = await fetch(`${BASE_URL}/orders`, {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (res.status === 201) {
const { data } = await res.json();
console.log("EIN filing submitted! Order ID:", data.order_id);
return data;
}
const err = await res.json().catch(() => ({}));
console.log("Submission failed:", err?.error?.message ?? "Unknown error");
return null;
}func submitEinApplication(payload map[string]interface{}, accessToken string) map[string]interface{} {
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseURL+"/orders", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil }
defer resp.Body.Close()
if resp.StatusCode == 201 {
var result struct { Data map[string]interface{} `json:"data"` }
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println("EIN filing submitted! Order ID:", result.Data["order_id"])
return result.Data
}
var errResp struct { Error struct { Message string `json:"message"` } `json:"error"` }
json.NewDecoder(resp.Body).Decode(&errResp)
fmt.Println("Submission failed:", errResp.Error.Message)
return nil
}async Task<JsonElement?> SubmitEinApplication(object payload, string token)
{
var json = JsonSerializer.Serialize(payload);
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await client.PostAsync($"{BaseUrl}/orders", content);
if (res.StatusCode == System.Net.HttpStatusCode.Created)
{
var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
var data = doc.RootElement.GetProperty("data");
Console.WriteLine($"EIN filing submitted! Order ID: {data.GetProperty("order_id").GetString()}");
return data;
}
var errDoc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
var errMsg = errDoc.RootElement.TryGetProperty("error", out var errE) ? errE.GetProperty("message").GetString() : "Unknown error";
Console.WriteLine($"Submission failed: {errMsg}");
return null;
}Step 6: Receive the EIN
Fulfilment is handled by SingleFile's filing team. Once the EIN is issued, the IRS confirmation letter is attached to the entity as a document and the order is completed. Register a webhook endpoint to be notified:
| Event | Meaning |
|---|---|
order.submitted | Your order was received |
document.created | A document was added to the entity. For EIN orders this is the IRS EIN letter, with document_type EIN |
order.completed | The EIN order is finished |
order.cancelled | The order was cancelled |
The EIN number itself is on the IRS letter. To fetch it, list the entity's documents and download the one with document_type EIN:
# List documents on the entity
curl -X GET "https://api.demo.singlefile.ai/external-api/v1/entities/${ENTITY_ID}/documents" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
# Fetch one document; the response includes a document_url to download the file
curl -X GET "https://api.demo.singlefile.ai/external-api/v1/documents/${DOCUMENT_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"def get_ein_letter(entity_id, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
documents = requests.get(f"{BASE_URL}/entities/{entity_id}/documents", headers=headers).json()["data"]
ein_docs = [d for d in documents if d["document_type"] == "EIN"]
if not ein_docs:
return None
detail = requests.get(f"{BASE_URL}/documents/{ein_docs[0]['id']}", headers=headers).json()["data"]
return detail["document_url"]async function getEinLetter(entityId, accessToken) {
const headers = { Authorization: `Bearer ${accessToken}` };
const { data: documents } = await (await fetch(`${BASE_URL}/entities/${entityId}/documents`, { headers })).json();
const einDoc = documents.find((d) => d.document_type === "EIN");
if (!einDoc) return null;
const { data: detail } = await (await fetch(`${BASE_URL}/documents/${einDoc.id}`, { headers })).json();
return detail.document_url;
}You can also poll GET /external-api/v1/orders/{order_id}; its status moves through received, submitted and completed, and status_history records each transition with a timestamp.
Complete EIN Workflow
def complete_ein_workflow(organization_id, entity_name, entity_type, ss4_pdf_path, access_token):
entity_id = get_existing_entity(organization_id, entity_name, access_token)
ein_schema = get_ein_schema(entity_type, access_token) # inspect "required" if the contract changes
ein_payload = create_ein_payload(entity_id, entity_type, ss4_pdf_path)
if not preview_ein_application(ein_payload, access_token):
raise Exception("EIN filing preview failed")
submitted_order = submit_ein_application(ein_payload, access_token)
if not submitted_order:
raise Exception("EIN filing submission failed")
return {"entity_id": entity_id, "order_id": submitted_order["order_id"]}
result = complete_ein_workflow(
organization_id="your_organization_uuid",
entity_name="Acme AI Solutions LLC",
entity_type="llc",
ss4_pdf_path="ss4.pdf",
access_token=ACCESS_TOKEN,
)async function completeEinWorkflow(organizationId, entityName, entityType, ss4PdfPath, accessToken) {
const entityId = await getExistingEntity(organizationId, entityName, accessToken);
await getEinSchema(entityType, accessToken); // inspect "required" if the contract changes
const einPayload = await createEinPayload(entityId, entityType, ss4PdfPath);
if (!(await previewEinApplication(einPayload, accessToken))) throw new Error("EIN filing preview failed");
const order = await submitEinApplication(einPayload, accessToken);
if (!order) throw new Error("EIN filing submission failed");
return { entity_id: entityId, order_id: order.order_id };
}Common Errors
| Response | Cause | Fix |
|---|---|---|
400 SCHEMA_VALIDATION_ERROR with "<field> is required" | A required field from the schema is missing | Add the field; the errors array names each one |
400 on entity with "You must provide one of: entity_type or id or jurisdiction or name or organization" | entity.id is not a UUID string (for example a numeric id), or the object mixes id with other fields | Send { "id": "<uuid>" } only |
500 UNKNOWN_ERROR on preview or submit | signer_name is missing (see the callout at the top) | Add the top-level signer_name string |
Updated 6 days ago

