Skip to content

Import a service's price table from CSV

Open .md

POST/v1/api/store/{team_slug}/imports/csv

v1Operation ID v1_public_imports_csv_store

Uploads a price table as a CSV file and saves it on a service in one request.

  • target_mode=existing_service: permanently overwrites the service's whole price table. Any existing options or price breaks missing from the file are deleted. The service's name and locations do not change.
  • target_mode=create: creates a new service with these prices.

Send multipart/form-data. The import runs while you wait, and it is all or nothing. If anything in the file is wrong, you get 422 with the first problem found (a row problem names its line), and nothing is saved: no price changes and no import job. Otherwise the response is the finished import job, always completed with failed_item_count 0. You can read it again later with GET /v1/api/store/{team_slug}/imports/{hash_id}. There is never a file of failed rows for these imports.

Only service pricing is accepted here (entity_type=service_pricing). Customer, vendor and product CSV files are imported in the YoPrint web app's Import Portal.

The file. Start from PUT /v1/api/store/{team_slug}/csv/template/service_pricing.

  • A .csv file of at most 6,240 KB, with at most 600 data rows and at least one, in UTF-8.
  • The first header must be exactly qty/type. Add at least one more column; each is an option of the service (for example 1 Color). Header names can't be blank or repeated.
  • Each row is a price break. qty/type is a number, the first row's must be 1, and no quantity can repeat.
  • Every cell needs a value. A cell is one price (12.50) or two, first|additional (12.50|9.00). Prices are numbers with at most 3 decimal places, written with a digit before the point (0.50, not .50) and no spaces.

Permission

To replace an existing service's prices: permission to update services. To create a service: permission to create and to update services.

Business rules

  • entity_type must be service_pricing. Any other value is a 422.
  • target_mode is existing_service or create. With existing_service, send service_id. With create, send name, allow_cust_loc, pricing_type and at least one entry in locations.
  • Every location id must be one of your team's product locations, each listed once, or the request fails with "One or more service locations are invalid."
  • The file must have the .csv extension and be at most 6,240 KB. It must hold at least one and at most 600 data rows.
  • First header exactly qty/type, then at least one option column. Option headers can't be blank or repeated.
  • Each qty/type value is a number, the first data row's is 1, and quantities can't repeat.
  • No empty cells (0 is a value). Each cell is a price, or first|additional. Prices are numbers with at most 3 decimal places, between -2,147,483,648 and 2,147,483,648. Write a digit before the decimal point and no spaces: .50 or 12 passes the number check but then fails with a server error.
  • All or nothing: the whole file is checked before anything is saved, and any problem stops the import with nothing saved. A returned job is always completed, with one item and failed_item_count 0.
  • One price import per service at a time, and one per new service name. A second upload waits up to 45 seconds for the first to finish; if the first is still running then, it fails with a server error. Retry after the first finishes.

Side effects

  • existing_service: permanently overwrites the service's whole price table with the file. Options and price breaks missing from the file are deleted. The service's name and locations do not change.
  • create: creates a service at the end of your service list, links the locations, then saves the prices.
  • If the service's pricing type is not additional, the second price in first|additional cells is ignored and the service keeps no additional-location prices.
  • A successful import is recorded as an import job, with the uploaded file attached. A failed one leaves no job: it does not appear in the import jobs list.
  • People with YoPrint open see the service and the import update live.

Path parameters

  • team_slugstringrequired

    Your team slug, shown as Team ID next to your API key in Profile.

    • Example your-team

Request body

multipart/form-data, required.

Which service gets the prices?

Send one of these 2 bodies.

An existing service

Permanently overwrites the service's whole price table with the file; omitted options and breaks are deleted.

  • entity_typestringrequired
    • Always "service_pricing"
  • target_modestringrequired
    • Always "existing_service"
  • filestring (binary)required

    The CSV file: a name ending in .csv, at most 6,240 KB and 600 data rows.

  • service_idstringrequired

    The service's id (a hash id), for example the service_id of a job's service.

Example request

curl
curl -X POST 'https://secure.yoprint.com/v1/api/store/your-team/imports/csv' \  -H 'Authorization: Bearer YOUR_API_KEY' \  -H 'Accept: application/json' \  -H 'X-Yoprint-Decimal-Format: exact-v1' \  -H 'Content-Type: application/json' \  -d '{    "entity_type": "service_pricing",    "file": "qty/type,1 Color,2 Color\n1,10.00|11.00,12.00|13.00\n12,8.50|9.00,10.00|11.00\n",    "service_id": "IIVE3hadAUOmIA",    "target_mode": "existing_service"  }'

A new service

Creates a service with this name, these locations and these prices.

  • entity_typestringrequired
    • Always "service_pricing"
  • target_modestringrequired
    • Always "create"
  • allow_cust_locstringrequired

    "Allow custom locations on jobs": 1 for yes, 0 for no. A form sends text, and true or false are refused.

    • One of: "1", "0"
  • filestring (binary)required

    The CSV file: a name ending in .csv, at most 6,240 KB and 600 data rows.

  • locationsarray of objectsrequired

    The product locations the service applies to, at least one, each once. Send each id as its own form field with a numbered index: locations[0][id], locations[1][id], and so on. A JSON text in a single locations field is refused (locations must be an array).

    • At least 1 item
    Fields of each item (1)
    • idstringrequired

      A product location's id (a hash id) from GET /v1/api/store/{team_slug}/service_location.

  • namestringrequired

    The new service's name.

  • pricing_typestringrequired

    single: per-item pricing. additional: per-item pricing with a second price for each additional location. single_flat: a flat rate per job.

    • One of: "single", "additional", "single_flat"

Example request

curl
curl -X POST 'https://secure.yoprint.com/v1/api/store/your-team/imports/csv' \  -H 'Authorization: Bearer YOUR_API_KEY' \  -H 'Accept: application/json' \  -H 'X-Yoprint-Decimal-Format: exact-v1' \  -H 'Content-Type: application/json' \  -d '{    "allow_cust_loc": "1",    "entity_type": "service_pricing",    "file": "qty/type,1 Color,2 Color\n1,10.00|11.00,12.00|13.00\n12,8.50|9.00,10.00|11.00\n",    "locations": [      {        "id": "454DGJ3i6EQpq0"      },      {        "id": "XDz01LsIUDpguv"      }    ],    "name": "Puff print",    "pricing_type": "additional",    "target_mode": "create"  }'

Response

200Successful response.

  • dataobjectrequired
    Child fields (20)
    • created_atstring (date-time)required
    • display_statusstringrequired

      A status slug formatted for UI display: matches status, or is imported_with_unresolved_rows when some items succeeded and some failed.

    • entity_typestringrequired

      What was imported. For CSV imports: service_pricing (the CSV type supported via API), or older uploader types (customer, vendor, simple_product, variant_product, custom_product). For Printavo imports: Printavo record types (such as customer, vendor).

    • failed_item_countintegerrequired

      Items that failed. Always 0 on service pricing imports: they are all or nothing, and any error fails with 422 without creating an import job. Only Printavo imports and retired CSV uploader jobs can have failed items.

    • idstringrequired

      The import job's id (a hash id).

    • source_typestringrequired

      Where the data came from.

      • One of: "csv", "printavo"
    • statusstringrequired

      completed, completed_with_errors and failed are final.

      • One of: "pending", "staged", "ready_to_commit", "committing", "completed", "completed_with_errors", "failed"
    • success_item_countintegerrequired

      Items saved.

    • total_item_countintegerrequired

      Rows (items) in the job. A service pricing job has one item: the whole price table.

    • Items skipped because they were imported before.

    • True when the job saves its rows without a separate review step.

    • completed_atstring (date-time) | null

      When the job reached a final status.

    • A human-readable status label: "Imported with unresolved rows" when display_status is imported_with_unresolved_rows, otherwise null.

    • Items skipped as duplicates within the job.

    • error_csv_urlstring | null

      Web app download URL for a failed-rows CSV. Null for service pricing and Printavo jobs, and for new imports. Only retired CSV uploader jobs (pre-September 2026) may have a link. Because this is a web app link that API keys cannot open, download the file via the API using GET /v1/api/store/{team_slug}/imports/{hash_id}/artifacts/error-csv. Check failed_item_count to see if items failed.

    • legacy_statusstring | null

      An older status word kept for the web app, such as import_success or validation_failed. Prefer status.

    • source_account_refstring | null

      The connected source account, for Printavo jobs.

    • target_modestring | null

      Service pricing jobs: existing_service or create. Otherwise usually null.

    • Failed items: the same number as failed_item_count.

    • updated_atstring (date-time) | null
  • statusintegerrequired
  • successbooleanrequired

Errors

  • 401The API key is missing or wrong (unauthenticated).

  • 403You are not allowed to do this. api_access_not_in_plan: your team's plan doesn't include API access (Pro and above, with an active subscription or trial), so every request answers this until the team upgrades. When your role lacks the permission the operation needs, the body is only {"message": "Insufficient Permission"}. Other refusals use the error envelope, with unauthorized or user_is_not_authorized.

    When
    You lack the service permissions the target mode needs (a plain {"message": "Insufficient Permission"} body, not the usual envelope). Checked early, before target_mode, the service and the file's contents; without a valid target_mode it asks for both create and update.
    Fix
    Ask a team admin for permission to create or update services.
  • 404Not found. page_not_found: the team slug is unknown, you are not a member of the team, or an id in the path is not one of this team's. code: null, with a message that starts "No query results for": there is no record with that id.

    When
    service_id matches no service of this team.
    Fix
    Check the service's id.
  • 422The request was refused and changed nothing. validation_failed lists what to fix in error.fields; other codes name a business rule, explained in error.message. A few checks answer with only {"message": "..."}. An operation that calls another service (a payment provider, a carrier, an accounting app) can answer connect_timeout ("Please retry again") when that service does not answer in time: the change may or may not have happened there, so read the record back before you retry.

    When
    A field is missing or invalid, the file is not .csv or is over 6,240 KB, or a location id is not yours (validation_failed; error.fields names the field).
    Fix
    Fix the field and send again.
    When
    The file breaks a rule: error.code single_error, the problem in error.message, and error.detail, a list holding the same text. A row problem starts with its line, as in "[LINE 3] Duplicate Quantity Break Detected: 12"; header, encoding and empty-file problems have no line.
    Fix
    Fix the file and upload it again. Nothing was saved.
    When
    The file has more than 600 data rows: a plain {"message": "This import expands to 601 source rows. The limit for service_pricing imports is 600 source rows per request."} body, not the usual envelope.
    Fix
    Split the table, or remove unused price breaks.
  • 500A business rule refused the request (see the operation's errors), or something failed inside YoPrint. Refusals use the error envelope with a code such as action_not_allowed; an unexpected failure answers {"message": "Server Error"}. Repeating a refusal gets the same answer; after an unexpected failure, read the record back before you retry a change.

    When
    A price or quantity is written like .50 or with a leading space, or a second upload for the same service (or the same new service name) waited 45 seconds and the first was still running ({"message": "Server Error"}). Nothing was saved.
    Fix
    Write numbers like 0.50 with no spaces; for the second case, retry after the first upload finishes.

Notes

  • Re-uploading the same file to the same service is safe: the result is the same table. With create, each successful upload makes another service, even with the same name.
  • Line numbers in error messages count the header as line 1, so the first data row is line 2.
  • Prices are saved as given, with up to 3 decimal places.
  • The public API has no operation that lists services. A job's services carry their service_id; take it from there or from YoPrint. Product location ids come from GET /v1/api/store/{team_slug}/service_location.