Skip to content
firmus.shdocs

Every page's text, headings and title. Code samples are not indexed.

Export a segment

Write the companies of a saved segment to a CSV, as a file job, all of them or the first `limit`, checked against the month's exported rows before it starts.

An export writes the companies a saved segment may list — its exportable_count, never its count — to a CSV, in the background. It is a file job of operation segment_export: the answer is 202 Accepted with the job, and its URL in Location; you read it at GET /v1/{country_code}/jobs/{id} and download it at /jobs/{id}/result or its download_url, like any job. POST /v1/{country_code}/jobs does not accept this operation. Only a saved segment can be exported: save it first with save: true.

fields is optional and takes enrich's fields and themes. Without it the file carries name, legal_form, registered_on, activity, workforce, size_category and head_office_address. Every export reads each company's head office whatever fields says, because a company whose head office is partially disclosed is never written; the head office's columns are in the file only when you select them.

limit is optional too: at most that many rows written, the first in SIREN order rather than a spread like the sample. A company withheld at writing time does not use up the limit. Without it, the export writes every company the segment may list. An unknown body key, a field enrich does not accept, or a limit that is not a whole number of 1 or more is 400 invalid_request.

Submit, wait, download

The block sets the key, submits segment 1, then reads the job until it is final and stops on any refusal, which curl -f turns into an empty $JOB rather than a loop that never ends:

read -rs FIRMUS_KEY && export FIRMUS_KEY

JOB_URL=$(curl -s -o job.json -w '%header{location}' -X POST \
  -H "Authorization: Bearer $FIRMUS_KEY" \
  -H "Content-Type: application/json" \
  -d '{}' \
  https://api.firmus.sh/v1/fr/segments/1/export)
cat job.json

while JOB=$(curl -sf -H "Authorization: Bearer $FIRMUS_KEY" "$JOB_URL") &&
  ! printf '%s' "$JOB" | grep -Eq '"status":"(succeeded|failed)"'; do
  sleep 5
done
printf '%s\n' "$JOB"

curl -sf -H "Authorization: Bearer $FIRMUS_KEY" -o segment.csv "$JOB_URL/result"

A refused submission has no Location, so $JOB_URL is empty and job.json holds the error. The job carries what a file job carries, and three things more:

  • counts.segment_export: exported, the rows written; withheld, companies that became partially disclosed (the company or its head office) or a sole trader since the count, or whose record could not be read in full, and so were not written; not_found, companies gone from the registry since the count.
  • exported_rows, what the job has counted against the month's exported rows so far. A withheld or not-found company is not counted. agent_rows stays at 0: an export spends no agent rows.
  • segment, the segment as it was when the export was admitted — id, name, filters, as_of (the day its windows counted from), include_sole_traders, acknowledgement (with the text of its version), limit (null without one) — and notice, the RNE caveat. Deleting or replacing the saved segment does not change a file being written.

progress.total_rows is what the export was admitted for — the exportable_count, or the limit when it is smaller — and processed_rows the companies read so far; exported can end below total_rows, never above it. An export never writes or counts more rows than it was admitted for: companies that join the segment after admission wait for the next export.

The file

UTF-8 with a byte-order mark, comma-separated, named segment-{id}-firmus.csv. It does not start with its header. The first lines are notices, one cell each, starting with #:

  1. the RNE caveat: segments are built from INSEE Sirene and do not reflect an opposition to prospecting recorded only in the RNE;
  2. the segment, its filters and the day its windows counted from;
  3. on a segment saved with sole traders only, the acknowledgement, its version, who gave it and when.

Then the header and one line per company. Skip the lines that start with # before handing the file to a CSV reader or a CRM import, or the first notice becomes your header. The columns are firmus_registry_number, firmus_enrich_status, the columns of the selected fields as a file job that enriches writes them, firmus_data_updated_at (the registry's timestamp for the record, not an import time), firmus_enrich_message, firmus_partial, firmus_partially_disclosed and firmus_sources. firmus_partially_disclosed is always false, since such a unit is never written; the column says so in the file that reaches the CRM. A cell a spreadsheet would run as a formula is written behind a leading apostrophe, as in every job result.

The job, its result and its download_url are kept seven days, until expires_at, then are 404.

Exported rows

The export is checked up front, for the whole segment: its exportable_count today, or the limit when it is smaller, must fit in the month's exported rows left, counting as spent what your organization's unfinished exports were admitted for. The month holds 1 000 exported rows on Free, 25 000 on Pro and 250 000 on Scale, on a meter of their own: exports do not spend agent rows, and file jobs do not spend exported rows. A segment that does not fit is refused and no job is created; message gives the rows left, which is the largest limit that would be admitted:

{
  "error": {
    "status": 402,
    "code": "quota_exceeded",
    "message": "This export would write up to 1840 rows, and this organization has 1000 exported rows left this month. Send `limit` (at most 1000) to export the first rows, in SIREN order, narrow the segment, or wait for the allowance to reset at the start of the next calendar month, UTC.",
    "documentation_url": "https://docs.firmus.sh/reference/errors#quota_exceeded"
  }
}

Retry-After counts the seconds to the start of the next calendar month, UTC. Only the rows written are counted. Submitting is one call — on Free, one of the month's 1 000 lookups; reading the job and downloading the file are not calls.

Errors

  • 400 invalid_request — an unknown body key, a field enrich does not accept, or a limit that is not a whole number of 1 or more.
  • 402 quota_exceeded — the segment, or its limit, does not fit in the month's exported rows, as above, or on Free the month's lookups are spent. Both carry Retry-After.
  • 403 public_key_forbidden — a publishable fk_pub key holding read.
  • 404 not_found — a segment that is not one of your organization's, or a country other than fr.
  • 503 segment_index_unavailable — this server cannot count segments yet; no job is created.

Next

  • Get a job — the job's status, progress and links.
  • Errors — every code above.
POST/v1/{country_code}/segments/{id}/exportScope read

Parameters

ParameterInTypeRequiredExample
country_codepathstringYesfr
idpathintegerYes1

Request

bash
curl -X POST -H "Authorization: Bearer $FIRMUS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fields":["identity","activity","size","head_office_address"]}' \
  "https://api.firmus.sh/v1/fr/segments/1/export"

Responses

Writes the companies of a saved segment to a CSV, as a file job: 202 with the job, read at /v1/{country_code}/jobs/{id}. Partially disclosed companies are never written, sole traders only under the segment's acknowledgement. The file opens on `#` notice lines (the RNE caveat, the segment, the acknowledgement), then the header. The whole export is checked against the plan's exported rows for the month before it starts. One call; the rows on their own meter.

{
  "id": "job_Seg111111111111111111111",
  "status": "queued",
  "operation": "segment_export",
  "filename": "segment-1.csv",
  "mapping": {},
  "mapping_detected": false,
  "fields": [
    "name",
    "legal_form",
    "registered_on",
    "activity",
    "workforce",
    "size_category",
    "head_office_address"
  ],
  "progress": {
    "total_rows": 2,
    "processed_rows": 0,
    "partial_rows": 0
  },
  "counts": {
    "resolve": null,
    "enrich": null,
    "segment_export": {
      "exported": 0,
      "withheld": 0,
      "not_found": 0
    }
  },
  "agent_rows": {
    "resolve": 0,
    "enrich": 0
  },
  "exported_rows": 0,
  "segment": {
    "id": 1,
    "name": "Software",
    "filters": {
      "activity": [
        "62"
      ]
    },
    "as_of": "2026-11-01",
    "include_sole_traders": false,
    "acknowledgement": null,
    "notice": "Built from INSEE Sirene. It does not reflect an opposition to prospecting recorded only in the RNE (diffusionCommerciale), which Firmus does not read yet. Partially disclosed companies are counted but never listed.",
    "limit": null
  },
  "error": null,
  "result_url": null,
  "download_url": null,
  "created_at": "2026-11-01T09:00:00Z",
  "started_at": null,
  "finished_at": null,
  "expires_at": "2026-11-08T09:00:00Z"
}
  • idstring
  • statusstring
  • operationstring
  • filenamestring
  • mappingobject
  • mapping_detectedboolean
  • fieldsstring[]
    • []stringoptional
  • progressobject
    • total_rowsinteger
    • processed_rowsinteger
    • partial_rowsinteger
  • countsobject
    • resolveunspecifiednullable
    • enrichunspecifiednullable
    • segment_exportobject
      • exportedinteger
      • withheldinteger
      • not_foundinteger
  • agent_rowsobject
    • resolveinteger
    • enrichinteger
  • exported_rowsinteger
  • segmentobject
    • idinteger
    • namestring
    • filtersobject
      • activitystring[]
        • []stringoptional
    • as_ofstring
    • include_sole_tradersboolean
    • acknowledgementobjectnullable
      • versionstring
      • acknowledged_atstring
      • acknowledged_bystring
      • textstringoptional
    • noticestring
    • limitintegeroptionalnullable
  • errorunspecifiednullable
  • result_urlunspecifiednullable
  • download_urlunspecifiednullable
  • created_atstring
  • started_atunspecifiednullable
  • finished_atunspecifiednullable
  • expires_atstring